Java 内置接口 Serializable示例详解

目录
  • 引言
    • Serializable 接口
    • Serializable 是一个标记型接口
    • serializable Version UID
    • Java 序列化与JSON序列化的区别
    • Java序列化相较于 JSON 的优势
    • Java 类对象的序列化代码演示
  • 总结

引言

上一部分我们着重讲了 Java 集合框架中在开发项目时经常会被用到的数据容器,在讲解、演示使用实践的同时,把这个过程中遇到的各种相关知识点:泛型、LambadaStream 操作,一并给大家做了梳理。

从这篇开始我们进入下一部分,用三到五部分给大家梳理一下,在用 Java 编程时,那些我们绕不开的 interface;从最基本的 Serializable ComparableIterator 这些,再到 Java 为了支持函数式编程而提供的 FunctionPredicateinterface

这些 Java 内置提供的 interface 或多或少我们在写 Java 代码的时候都见过,有的甚至是潜移默化地在日常编码中已经实现过其中的一些 interface,只不过我们没有察觉到罢了。相信通过阅读着几篇文章,一定会让你在写 Java 代码时更清楚自己是在做什么,不会再被这些个似曾相识的 interface 困扰到。

本文大纲如下:

Serializable 接口

作为 Java 中那些绕不开的内置接口 这个小系列的开篇文章,首先要给大家介绍的 interface 是 Serializable

Serializable这个接口的全限定名(包名 + 接口名)是 java.io.Serializable,这里给大家说个小技巧,当你看到一个类或者接口的包名前缀里包含java.io那就证明这个类 / 接口它跟数据的传输有关。

Serializable 是 Java 中非常重要的一个接口,如果一个类的对象是可序列化的,即对象在程序里可以进行序列化和反序列化,对象的类就一定要实现Serializable接口。那么为什么要进行序列化和反序列化呢?

序列化的意思是将对象的状态转换为字节流;反序列化则相反。换句话说,序列化是将 Java 对象转换为静态字节流(序列),然后我们可以将其保存到文件、数据库或者是通过通过网络传输,反序列化则是在我们读取到字节流后再转换成 Java 对象的过程;这也正好解释了为什么Serializable 接口会归属到java.io包下面。

Serializable 是一个标记型接口

虽说需要进行序列化的对象,它们的类都需要实现 Serializable 接口,但其实你会发现,我们在让一个类实现 Serializable 接口时,并没有额外实现过什么抽线方法。

import java.io.Serializable;
public class Person implements Serializable {
    private String name;
    private int age;
}

比如向上面个类文件里的内容,Person 类声明实现 Serializable 接口后,并没有去实现什么抽象方法,IDE 也不会用红线警告提示我们:“你有一个抽象方法需要实现” ,原因是 Serializable 接口里并没有声明抽象方法。

public interface Serializable {
}

这种不包含任何方法的 interface 被称为标记型接口,类实现 Serializable接口不必实现任何特定方法,它只起标记作用,让 Java 知道该类可以用于对象序列化。

serializable Version UID

虽说一个类实现了 Serializable 接口的时候不需要实现特定的方法,但是经常会看到一些实现了Serializable的类中,都有一个名为serialVersionUID类型为long的私有静态 属性。

import java.io.Serializable;
public static class Person implements Serializable {
    private static final long serialVersionUID = -7792628363939354385L;
    public String name;
    public int    age;
}

该属性修饰符里使用了final即赋值后不可更改。Java 的对象序列化 API 在从读取到的字节序列中反序列化出对象时,使用 serialVersionUID 这个静态类属性来判断:是否序列化对象时使用了当前相同版本的类进行的序列化。Java 使用它来验证保存和加载的对象是否具有相同的属性,确保在序列化上是兼容的。

大多数的 IDE 都可以自动生成这个 serialVersionUID静态属性的值,规则是基于类名、属性和相关的访问修饰符。任何更改都会导致不同的数字,并可能导致 InvalidClassException。 如果一个实现 Serializable 的类没有声明 serialVersionUID,JVM 会在运行时自动生成一个。但是,强烈建议每个可序列化类都声明 serialVersionUID,因为默认生成的serialVersionUID依赖于编译器,因此可能会导致意外的InvalidClassExceptions

我上面那个例子里,Person 类的serialVersionUID是用 Intelij IDEA 自动生成的,所以值看起来一大串,不是我自己些的。IDEA 默认不会给可序列化类自动生成 serialVersionUID 需要安装一个插件。

这里给大家放一个截图,插件的安装和使用,网上有很多例子,大家需要的话动手搜一下,这里就不再占用太多篇幅讲怎么安装和使用这个插件了。

Java 序列化与JSON序列化的区别

Java 的序列化与现在互联网上 Web 应用交互数据常用的 JSON 序列化并不是一回事儿,这是咱们需要注意的,像 Java、C#、PHP 这些编程语言,都有自己的序列化机制把自家的对象序列化成字节然后进行传输或者保存,但是这些语言的序列化机制之间并不能互认,即用 Java 把对象序列化成字节、通过网络 RESTful API 传给一个 PHP 开发的服务,PHP 是没办法反序列化还原出这个对象的。这样才有了 JSON、XML、Protocol Buffer 这样的更通用的序列化标准。

例如在实际项目开发的时候,Java 对象往往被序列化为 JSON、XML 后再在网络上传输,如果对数据大小敏感的场景,会把 Java 对象序列化成空间占用更小的一些二进制格式,比如 Protocol Buffer ( 分布式 RPC 框架 gRPC 的数据交换格式)。这样做的好处是序列化后的数据可以被非 Java 应用程序读取和反序列化,例如,在 Web 浏览器中运行的 JavaScript 可以在本地将对象序列化成 JSON 传输给 Java 写的 API 接口,也可以从 Java API接口返回响应中的 JSON 数据,反序列化成 JavaScript 本地的对象 。

像上面列举的这些对象序列化机制,是不需要我们的 Java 类实现 Serializable 接口的。这些 JSON、XML 等格式的序列化类,通常使用 Java 反射来检查类,配合一些特定的注解完成序列化。

Java序列化相较于 JSON 的优势

上面介绍了 JSON 这样的通用序列化格式的优势,有的可能会问了,那还用 Java 序列化干啥。这里再给大家分析一下,Java 对象序列化虽然在通用性上不如 JSON 那些序列化格式,但是在 Java 生态内部却是十分好用的,其最聪明的一点是,它不仅能保存对象的副本,而且还会跟着对象里面的reference,把它所引用的对象也保存起来,然后再继续跟踪那些对象的reference,以此类推。

这个机制所涵盖的范围不仅包括对象的成员数据,而且还包含数组里面的reference。如果你要自己实现对象序列化的话,那么编写跟踪这些链接的程序将会是一件非常痛苦的任务。但是,Java的对象序列化就能精确无误地做到这一点,毫无疑问,它的遍历算法是做过优化的。

另外你们在一些资料里看过 Java Bean 的定义

1、所有属性为private

2、提供默认构造方法

3、提供getter和setter

4、实现java.io.Serializable接口

那么问题来了,为什么要进行序列化?每个实体bean都必须实现serializabel接口吗?以及我做项目的时候,没有实现序列化,同样没什么影响,到底什么时候应该进行序列化操作呢?

这里转载一个网上大佬对这个问题的解释

首先第一个问题,实现序列化的两个原因:

1、将对象的状态保存在存储媒体中以便可以在以后重新创建出完全相同的副本;

2、按值将对象从一个应用程序域发送至另一个应用程序域。实现serializabel接口的作用是就是可以把对象存到字节流,然后可以恢复,所以你想如果你的对象没实现序列化怎么才能进行持久化和网络传输呢,要持久化和网络传输就得转为字节流,所以在分布式应用中及设计数据持久化的场景中,你就得实现序列化。

第二个问题,是不是每个实体bean都要实现序列化,答案其实还要回归到第一个问题,那就是你的bean是否需要持久化存储媒体中以及是否需要传输给另一个应用,没有的话就不需要,例如我们利用fastjson将实体类转化成json字符串时,并不涉及到转化为字节流,所以其实跟序列化没有关系。

第三个问题,有的时候并没有实现序列化,依然可以持久化到数据库。这个其实我们可以看看实体类中常用的数据类型,例如Date、String等等,它们已经实现了序列化,而一些基本类型,数据库里面有与之对应的数据结构,从我们的类声明来看,我们没有实现serializabel接口,其实是在声明的各个不同变量的时候,由具体的数据类型帮助我们实现了序列化操作。

另外需要注意的是,在NoSql数据库中,并没有与我们Java基本类型对应的数据结构,所以在往nosql数据库中存储时,我们就必须将对象进行序列化,同时在网络传输中我们要注意到两个应用中javabean的serialVersionUID要保持一致,不然就不能正常的进行反序列化。

Java 类对象的序列化代码演示

到这里 Serializable 需要了解的基础知识就都给大家梳理出来了,这块属于选读,用 Java 编程写序列化代码的场景并不是太多,不过有兴趣就再接着往下看吧,有个印象,这样以后写代码的时候,哪天用上了,还能快速想起来在哪看过,再回来翻看。

Java 对象序列化(写入)由 ObjectOutputStream 完成,反序列化(读取)由 ObjectInputStream 完成。ObjectInputStream 和 ObjectOutputStream 是分别继承了 java.io.InputStream 和 java.io.OutputStream 抽象的实体类。 ObjectOutputStream 可以将对象的原型作为字节流写入 OutputStream。然后我们可以使用 ObjectInputStream 读取这些流。 ObjectOutputStream 中最重要的方法是:

public final void writeObject(Object o) throws IOException;

这个方法接收一个可序列化对象(实现了 Serializable 接口的类的对象)并将其转换为字节序列。同样,在ObjectInputStream 中最重要的方法是:

public final Object readObject() throws IOException, ClassNotFoundException;

此方法可以读取字节流并将其转换回 Java 对象。然后我们可以再使用类型转换(Type Cast)将其转换回原始的类型对象。

下面我们使用文章示例里的Person类再给大家演示一下 Java 的序列化代码。

public class Person implements Serializable {
    private static final long serialVersionUID = 1L;
    static String country = "ITALY";
    private int age;
    private String name;
    transient int height;
    // 省略 getter 和 setter
}

这里要注意一下, static 修饰的静态属性是类属性,并不属于对象,所以在序列化对象时不会把类中的静态属性序列化了,另外我们也可以使用 transient关键字修饰那些我们想在序列化过程中忽略调的对象属性。

@Test
public void serializingAndDeserializing_ThenObjectIsTheSame() ()
  throws IOException, ClassNotFoundException {
    Person person = new Person();
    person.setAge(20);
    person.setName("Joe");
    // 用指定文件路径--当前目录的 test_serialization.txt 文件创建 FileOutputStream。
    // 在写入 FileOutputStream 时, FileOutputStream 会在在项目目录中创建文件
    // “test_serialization.txt”
    FileOutputStream fileOutputStream
      = new FileOutputStream("./test_serialization.txt");
    // 以 FileOutputStream 为底层输出流创建对象输出流 ObjectOutputStream
    ObjectOutputStream objectOutputStream
      = new ObjectOutputStream(fileOutputStream);
    // 向 ObjectOutputStream 中写入 person 对象
    objectOutputStream.writeObject(person);
    // 把数据从流中刷到磁盘上
    objectOutputStream.flush();
    objectOutputStream.close();
    // 用上面的文件路径,创建文件输入流
    FileInputStream fileInputStream
      = new FileInputStream("./test_serialization.txt");
    // 以文件输入流创建对象输入流 ObjectInputStream
    ObjectInputStream objectInputStream
      = new ObjectInputStream(fileInputStream);
    // 用对象输入流读取到文件中保存的序列化对象,反序列化成 Java Object 再转换成 Person 对象
    Person p2 = (Person) objectInputStream.readObject();
    objectInputStream.close();
    assertTrue(p2.getAge() == person.getAge());
    assertTrue(p2.getName().equals(person.getName()));
}

上面这个单元测试里的代码演示了,怎么把 Person 类的对象进行 Java 序列化保存到文件中,再从文件中读取对象被序列化后的字节序列,然后还原成Person类的对象。

因为我们的专栏还没有设计到 Java IO 这块的内容,所以各种输入输出流就不过多进行讲解了,为了方便大家阅读时理解上面的程序,我在上面程序注释里已经详细注释了每一步完成的操作,这些输入输出流我们等到讲到 Java IO 体系的时候再详细进行讲解。

总结

今天给大家梳理了 Java Serializable 接口的一些必须要了解的知识,Serializable 接口在我们用 Java 编程的时候经常见,但是很多人并不了解它的作用,因为它的主要作用还是用于标记类是否是可序列化类,这样 Java 的 ObjectOutputStream 和 ObjectInputStream 才能对类的对象进行序列化和反序列化。

下一篇我们分享 Iterable 和 Iterator 这两个名字看起差不多的 Java 内置接口,请关注我们其它相关文章!

(0)

相关推荐

  • Java DefaultListableBeanFactory接口超详细介绍

    目录 前言 AliasRegistry SimpleAliasRegistry SingletonBeanRegistry DefaultSingletonBeanRegistry FactoryBeanRegistrySupport AbstractBeanFactory AbstractAutowireCapableBeanFactory BeanDefinitionRegistry ConfigurableListableBeanFactory 前言 本文,对bean工厂的接口做分析梳理具

  • Java中的Comparable和Comparator接口

    目录 一. Comparable接口 1. Comparable简介 2. 为什么要实现Comparable接口 3. Comparable的实际应用 二. Comparator接口 1. Comparator简介 2. Comparator接口的实际运用 三. Comparable和Comparator的比较 一. Comparable接口 1. Comparable简介 Comparable是排序接口. 若一个类实现了Comparable接口,就意味着该类支持排序. 实现了Comparabl

  • Java BeanDefination接口详细讲解

    目录 功能作用 为什么这样设计 假设没有BeanDefinition 有BeanDefinition BeanDefinition 属性介绍 示例 功能作用 BeanDefinition 是定义 Bean 的配置元信息接口,包含: Bean 的类名 设置父 bean 名称.是否为 primary. Bean 行为配置信息,作用域.自动绑定模式.生命周期回调.延迟加载.初始方法.销毁方法等 Bean 之间的依赖设置,dependencies 构造参数.属性设置 BeanDefinition 子类方

  • Java基础学习之接口详解

    目录 概述 定义格式 含有抽象方法 含有默认方法和静态方法 含有私有方法和私有静态方法 基本的实现 实现的概述 抽象方法的使用 默认方法的使用 静态方法的使用 私有方法的使用 接口的多实现 抽象方法 默认方法 静态方法 优先级的问题 接口的多继承 其他成员特点 概述 接口,是Java语言中一种引用类型,是方法的集合,如果说类的内部封装了成员变量.构造方法和成员方法,那么接口的内部主要就是封装了方法,包含抽象方法(JDK 7及以前),默认方法和静态方法(JDK 8),私有方法 (JDK 9). 接

  • java中如何使用HttpClient调用接口

    目录 java使用HttpClient调用接口 HttpClient 提供的主要的功能 直接言归正传了!!!!上代码 java的HttpClient调用远程接口 使用方法 实例 java使用HttpClient调用接口 HttpClient 提供的主要的功能 (1)实现了所有 HTTP 的方法(GET,POST,PUT,DELETE 等) (2)支持自动转向 (3)支持 HTTPS 协议 (4)支持代理服务器等 直接言归正传了!!!!上代码 public static String sendPu

  • Java Map接口概述和常用方法详解

    目录 概述 Map常用子类 Map接口中的常用方法 Map集合遍历键找值方式 Entry键值对对象 Map集合遍历键值对方式 概述 现实生活中,我们常会看到这样的一种集合:IP地址与主机名,身份证号与个人,系统用户名与系统用户对象等,这种一一对应的关系,就叫做映射.Java提供了专门的集合类用来存放这种对象关系的对象,即java.util.Map接口. 我们通过查看Map接口描述,发现Map接口下的集合与Collection接口下的集合,它们存储数据的形式不同,如下图. Collection中的

  • Java如何实现http接口参数和返回值加密

    目录 参数和返回值得加密目的 具体实现方式 大致思路 代码实现 身份检验 参数和返回值得加密目的 为了保证接口不被人拦截下来恶意请求,保证程序的稳定性,我们可以使用接口加密的方法来保证参数和返回值的保密性. 具体实现方式 因为本人是写Java 的,所以这里就直接以Java代码为例.思想都是一样的,只是不同的语言都不同的实现方式. 大致思路 我们的参数和返回值只需要定义两个参数:ak,ct,ak存放堆成加密的秘钥,ct存放加密之后的请求内容. 加密方式用到AES对称加密和RSA非对称加密,将请求参

  • Java ServletContext与ServletConfig接口使用教程

    目录 ServletContext接口 1.概念 2.功能 1.获取Web应用程序的初始化参数 2.实现多个Servlet对象共享数据 3.读取Web应用下的资源文件 ServletConfig接口 1.概念 2.ServletConfig的常用方法 ServletContext接口 1.概念 当Servlet容器启动时,会为每个Web应用创建一个唯一的ServletContext对象代表当前Web应用,可以和程序的容器(服务器)来通信. 两种获取方式: 通过request对象获取 Servle

  • Java 内置接口 Serializable示例详解

    目录 引言 Serializable 接口 Serializable 是一个标记型接口 serializable Version UID Java 序列化与JSON序列化的区别 Java序列化相较于 JSON 的优势 Java 类对象的序列化代码演示 总结 引言 上一部分我们着重讲了 Java 集合框架中在开发项目时经常会被用到的数据容器,在讲解.演示使用实践的同时,把这个过程中遇到的各种相关知识点:泛型.Lambada.Stream 操作,一并给大家做了梳理. 从这篇开始我们进入下一部分,用三

  • Java 数据结构算法Collection接口迭代器示例详解

    目录 Java合集框架 Collection接口 迭代器 Java合集框架 数据结构是以某种形式将数据组织在一起的合集(collection).数据结构不仅存储数据,还支持访问和处理数据的操作 在面向对象的思想里,一种数据结构也被认为是一个容器(container)或者容器对象(container object),它是一个能存储其他对象的对象,这里的其他对象常被称为数据或者元素 定义一种数据结构从实质上讲就是定义一个类.数据结构类应该使用数据域存储数据,并提供方法支持查找.插入和删除等操作 Ja

  • springboot+mybatis-plus实现内置的CRUD使用详解

    springboot+mybatis-plus实现内置的CRUD使用详情,具体修改删除操作内容后文也有详细说明 mybatis-plus的特性 无侵入:只做增强不做改变,引入它不会对现有工程产生影响,如丝般顺滑 损耗小:启动即会自动注入基本CURD,性能基本无损耗,直接面向对象操作 强大的 CRUD操作:内置通用 Mapper.通用Service,仅仅通过少量配置即可实现单表大部分 CRUD 操作,更有强大的条件构造器,满足各类使用需求 支持 Lambda形式调用:通过 Lambda 表达式,方

  • SpringBoot内置tomcat启动原理详解

    前言 不得不说SpringBoot的开发者是在为大众程序猿谋福利,把大家都惯成了懒汉,xml不配置了,连tomcat也懒的配置了,典型的一键启动系统,那么tomcat在springboot是怎么启动的呢? 内置tomcat 开发阶段对我们来说使用内置的tomcat是非常够用了,当然也可以使用jetty. <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-bo

  • Java基本语法之内部类示例详解

    目录 1.内部类概念及分类 2.实例内部类 2.1实例内部类的创建 2.2使用.this和.new 2.3内部类实现迭代打印 2.4内部类的继承 3.静态内部类 4.匿名内部类 1.内部类概念及分类 将一个类定义在另一个类的内部或者接口内部或者方法体内部,这个类就被称为内部类,我们不妨将内部类所在的类称为外围类,除了定义在类,接口,方法中的内部类,还有一种特殊的内部类,那就是使用关键字new创建一个匿名类的对象,而这个匿名类其实就是一个内部类,具体说是一个匿名内部类,经常用于传入构造器实参构造对

  • Java垃圾回收机制的示例详解

    目录 一.概述 二.对象已死? 1.引用计数算法 2.可达性分析算法 3.四种引用 4.生存还是死亡? 5.回收方法区 三.垃圾收集算法 1.分代收集理论 2.名词解释 3.标记-清除算法 4.标记-复制算法 5.标记-整理算法 一.概述 说起垃圾收集(Garbage Collection,下文简称GC),有不少人把这项技术当作Java语言的伴生产 物.事实上,垃圾收集的历史远远比Java久远,在1960年诞生于麻省理工学院的Lisp是第一门开始使 用内存动态分配和垃圾收集技术的语言.当Lisp

  • java设计模式策略模式图文示例详解

    目录 策略模式 意图 问题 解决方案 真实世界类比 策略模式结构 伪代码 策略模式适合应用场景 实现方式 策略模式优缺点 策略模式优缺点 与其他模式的关系 策略模式 亦称:Strategy 意图 策略模式是一种行为设计模式,它能让你定义一系列算法,并将每种算法分别放入独立的类中,以使算法的对象能够相互替换. 问题 一天,你打算为游客们创建一款导游程序.该程序的核心功能是提供美观的地图,以帮助用户在任何城市中快速定位. 用户期待的程序新功能是自动路线规划:他们希望输入地址后就能在地图上看到前往目的

  • java 线性表接口的实例详解

    java 线性表接口的实例详解 前言: 线性表是其组成元素间具有线性关系的一种线性结构,对线性表的基本操作主要有插入.删除.查找.替换等,这些操作可以在线性表的任何位置进行.线性表可以采用顺序存储结构和链式存储结构表示. 本接口的类属于dataStructure包的linearList子包.线性表接口LList声明如下,描述线性表的取值.置值.插入.删除等基本操作. package dataStructure.linearList; public interface LList<E> { bo

  • Java设计模式之适配器模式的示例详解

    目录 定义 分类 案例 需求 方案一:类适配器 方案二:对象适配器 方案三:接口适配器 对比分析 方案一:类适配器 方案二:对象适配器 方案三:接口适配器 总结 定义 适配器模式,即将某个类的接口转换成客户端期望的另一个接口的表示,主要目的是实现兼容性,让原本因为接口不匹配,没办法一起工作的两个类,可以协同工作. 分类 类适配器 对象适配器 接口适配器 案例 需求 手机充电,通过手机充电器将220V电压适配为5V 方案一:类适配器 定义220V交流电(被适配者的角色) /** * 220V交流电

  • Java装饰者模式的示例详解

    目录 定义 案例 需求 方案 分析 使用场景 知识点补充 定义 装饰者模式:在不改变原有对象的基础之上,动态的将功能附加到对象上,提供了继承更有弹性的替代方案,也体现了开闭原则 案例 需求 一个人去咖啡店点了一杯卡布奇诺,加了一份热牛奶 方案 定义咖啡基类 public abstract class Coffee { private String desc; private float price; public abstract float cost(); public String getD

随机推荐