对象序列化的目标是将对象保存到磁盘中,或允许在网络中直接传输对象。序列化机制允许把内存中的Java对象转换为平台无关的二进制流,从而允许把这种二进制流保存在磁盘上,或通过网络进行传输。其他程序获取到二进制流就能将其恢复成原来的Java对象。
类可序列化必须实现如下两接口之一:
①Serializable
②Externalizable
实现Serializable接口
- writeObject()负责序列化,默认调用out.defaultWriteObject保存Java对象各实例变量。
- readObject()负责反序列化,默认调用in.defaultReadObject来恢复Java对象的非瞬态实例变量。
- readObjectNoData()用来正确初始化反序列化对象,如版本不同,流被篡改等。
- writeReplace()在writeObject()之前被调用,将对象序列化成其他对象。
- readResolve()在readObject()之后被调用,实现保护性复制整个对象,该方法返回值将代替原来反序列化的对象。所有单例类、枚举类在实现序列化时都应该提供此方法,这样才可以保证反序列化的对象依然正常。对于final类重新此方法不会有任何问题;否则,重新此方法应尽量使用private修饰。
实现Externalizable接口
实现该接口需要强制自定义序列化。
- readExternal(ObjectInput in):负责反序列化。
- writeExternal(ObjectOutput out):负责序列化。
对比
注意:对象的类名、实例变量(基本类型、数组及对其他对象的引用)都会被序列化;方法、类变量、transient实例变量都不会被序列化。
反序列化
反序列化读取的仅仅是Java对象的数据,而不是Java类,所以采用反序列化恢复Java对象时必须提供该Java对象所属类的class文件。反序列化无须通过构造器来初始化Java对象。
序列化算法
①所有保存在磁盘中的对象都有一个序列化编号;
②当程序试图序列化一个对象时,程序必须先检查该对象是否已经被序列化过:
未序列化,将对象转换成字节序列并输出;
已序列化,直接输出一个序列化编号,不再重新序列化该对象。
这样就存在一个问题,当程序序列化一个可变对象时,只有第一次调用writeObject()方法才会将对象转换为字节序列输出,再次调用writeObject()方法将只输出前面的序列化编号,即使实例变量已经修改,修改的值也不会被输出。
版本
由上可知,序列化对象时必须提供该对象的class文件,问题是,随着项目升级,class文件也会升级,如何保证两个class文件的兼容性?
Java序列化机制为序列化类提供了一个private static final的serialVersionUID,用于标识序列化版本。一个类升级后,只要该值不变,则被认为是同一个序列化版本。
为保证反序列化时版本的兼容性与移植性,最好自己定义serialVersionUID值。如果不显式定义此值,则JVM根据类相关信息进行计算。
如果修改了类的非瞬态实例变量,则可能导致序列化版本不兼容:
①对象流中的对象和新类中包含同名的实例变量,但实例类型不同,则序列化失败,类定义应该更新serialVersionUID值;
②对象流中对象比新类中包含更多实例变量,多出的实例变量被忽略,版本可兼容,无须更新serialVersionUID值;
③新类比对象流中的对象包含更多实例变量,版本可兼容,可不更新serialVersionUID,但反序列化得到的新对象中多出的实例变量都是null或0。