什么是Java序列化?

持久化内存中的对象到硬件设备,会把其状态保存为一组字节,在未来,再将这些字节组装成对象,这就是序列化和反序列化。

必须注意地是,对象序列化保存的是对象的"状态",即它的成员变量。由此可知,对象序列化不会关注类中的静态变量以及被transient关键字修饰的成员变量。

Java序列化的应用场景

把对象持久化到存储设备上

对象通过网络传输给其它客户端

基本知识点

Serializable概述

对于任何需要被序列化的对象,都必须要实现接口Serializable,它只是一个标识接口,本身没有任何成员,只是用来标识说明当前的实现类的对象可以被序列化.

如果父类实现序列化,子类自动实现序列化,不需要显式实现Serializable接口。

transient关键字

如果某实例变量不能或不应该被序列化,就把它标记为transient的变量,这样序列化程序就会把它跳过。

transient的引用变量会以null返回,基本数据类型会以相应的默认值返回。

(例如:引用类型没有实现Serializable,或者动态数据只可以在执行时求出而不能或不必存储)

serialVersionUID

作用:

用来辅助序列化和反序列化过程的,原则上序列化后的数据中的serialVersionUID只有和当前类的serialVersionUID相同才能够正常地被反序列化。

工作机制:

序列化的时候系统会把当前类的serialVersionUID写入序列化的文件中,当反序列化的时候系统会去检测文件中的serialVersionUID,看它是否和当前类的serialVersionUID一致,如果一致就说明序列化的类的版本和当前类的版本是相同的,这个时候可以成功反序列化;否则就说明当前类和序列化的类相比发生了某些变换,比如成员变量的数量,类型可能发生了改变,这个时候无法正常反序列化的。并会产生以下异常:

java.io.InvalidClassException: 实体类(pojo); local class incompatible: stream classdesc serialVersionUID = 812952289507407815, local class serialVersionUID = -7688346538714640295

两种指定serialVersionUID的方式:

手动指定serialVersionUID的值,比如100L

通过IDE根据当前类的结构自动去生成它的hash值。

指定与不指定serialVersionUID有什么不同的结果?

如果不手动指定serialVersionUID的值,反序列化时当前类有所改变,比如增加或者删除了某些成员变量,那么系统就会重新计算当前类的hash值并把它赋值给serialVersionUID,这个时候当前类的serialVersionUID就和序列化的数据中的serialVersionUID不一致,于是反序列化失败,程序就会抛出java.io.InvalidClassException。

如果手动指定了它以后,就可以在很大的程度上避免反序列化过程的失败。比如当版本升级后,我们可能删除了某个成员变量也可能增加了一些新的成员变量,这个时候我们的反序列化过程仍然能成功,程序仍然能够最大限度地恢复数据,相反,如果不指定serialVersionUID的话,程序则会crash。

如果类结构发生了非常规性改变,比如修改了类名,修改了成员变量的类型,这个时候尽管serialVersionUID验证通过,但是反序列化过程还是会失败,因为类的结构有了毁灭性的改变,根本无法从老版本的数据中还原出一个新的类结构的对象。

简单实例

File file = new File("box.out");
FileOutputStream fileOutputStream = new FileOutputStream(file);
ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);
objectOutputStream.writeObject(new Box(100, 100));
objectOutputStream.close();
FileInputStream fileInputStream = new FileInputStream(file);
ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream);
Box o = (Box) objectInputStream.readObject();
objectInputStream.close();
System.out.println(o);

注意事项

如果有不能被序列化的对象,执行期间就会抛出NotSerializableException异常;

序列化时,只对对象的状态进行保存,而不管对象的方法

静态变量和transient修饰的变量不会被序列化

如果子类实现Serializable接口而父类未实现时,父类不会被序列化,但此时父类必须有个无参构造方法,否则会抛InvalidClassException异常。因为反序列化时会恢复原有子对象的状态,而父类的成员变量也是原有子对象的一部分。由于父类没有实现序列化接口,即使没有显示调用,也会默认执行父类的无参构造函数使变量初始化。