概念
序列化:将对象写入到 IO 流中
反序列化:从 IO 流中恢复对象。反序列化并不会调用构造方法。反序列的对象是由JVM自己生成的对象,不通过构造方法生成。
序列化的意义:序列化机制允许将实现序列化的 Java 对象转换为字节序列,这些字节序列可以保存在磁盘上,或通过网络传输,以达到以后恢复成原来的对象的目的。序列化机制使得对象可以脱离程序的运行而独立存在。
使用场景:所有可在网络上传输的对象都必须是可序列化的,比如 RMI(remote method invoke,即远程方法调用),传入的参数或返回的对象都是可序列化的,否则会出错;所有需要保存到磁盘的 Java 对象都必须是可序列化的。
开发建议:程序创建的每个JavaBean 类都实现 Serializeable 接口。
注意:序列化对象保存的是对象的“状态”,即他的成员变量,但不会保存静态变量。
序列化算法
所有保存到磁盘的对象都有一个序列化编码号
当程序试图序列化一个对象时,会先检查此对象是否已经序列化过,只有此对象从未(在此虚拟机)被序列化过,才会将此对象序列化为字节序列输出。
如果此对象已经序列化过,则直接输出编号即可。
Transient
使用 transient 修饰的属性,java序列化时,会忽略掉此字段,所以反序列化出的对象,被 transient 修饰的属性是默认值。对于引用类型,值是 null;基本类型,值是 0;boolean 类型,值是 false。
Externalizable
Externalizable 接口不同于 Serializable 接口,实现此接口必须实现接口中的两个方法实现自定义序列化,这是强制性的;特别之处是必须提供 public 的无参构造器,因为在反序列化的时候需要反射创建对象。
虽然 Externalizable 接口带来了一定的性能提升,但变成复杂度也提高了,所以一般通过实现 Serializable 接口进行序列化。
serialVersionUID
序列化版本号可自由指定,如果不指定,JVM 会根据类信息自己计算一个版本号,这样随着 class 的升级,就无法正确反序列化;不指定版本号另一个明显隐患是,不利于 jvm 间的移植,可能 class 文件没有更改,但不同 jvm 可能计算的规则不一样,这样也会导致无法反序列化。
什么情况下需要修改serialVersionUID呢?分三种情况。
如果只是修改了方法,反序列化不容影响,则无需修改版本号
如果只是修改了静态变量,瞬态变量(transient修饰的变量),反序列化不受影响,无需修改版本号
如果修改了非瞬态变量,则可能导致反序列化失败。如果新类中实例变量的类型与序列化时类的类型不一致,则会反序列化失败,这时候需要更改serialVersionUID。如果只是新增了实例变量,则反序列化回来新增的是默认值;如果减少了实例变量,反序列化时会忽略掉减少的实例变量
总结
所有需要网络传输的对象都需要实现序列化接口,通过建议所有的javaBean都实现Serializable接口。
对象的类名、实例变量(包括基本类型,数组,对其他对象的引用)都会被序列化;方法、类变量、transient实例变量都不会被序列化。
如果想让某个变量不被序列化,使用transient修饰。
序列化对象的引用类型成员变量,也必须是可序列化的,否则,会报错。
反序列化时必须有序列化对象的class文件。
当通过文件、网络来读取序列化后的对象时,必须按照实际写入的顺序读取。
单例类序列化,需要重写readResolve()方法;否则会破坏单例原则。
同一对象序列化多次,只有第一次序列化为二进制流,以后都只是保存序列化编号,不会重复序列化。
建议所有可序列化的类加上serialVersionUID 版本号,方便项目升级。