序列化机制允许将实现序列化的Java对象转换成字节序列 ,这些字节序列可以被保存在磁盘上,或者通过网络传输,
以备以后重新恢复成原来的对象.
1.对象的序列化(serialize)指将一个Java对象写入IO流中,对象的反序列化机制(Deserialize)则指从IO流中恢复该Java对象.
如果需要让某个对象可以支持序列化机制,必须让它的类是可序列化的(实现Serialize接口或者Externalizable接口)
一.使用对象流实现序列化
ObjectOutputStream oos=new ObjectOutputStream( new FileOutputStream("..."));//输出流(处理流)
oos.writeObject(new Object())//对象输出到输出流中
二.使用对象流反序列化
ObjectInputStream ois=new ObjectInputStream( new FileInputStream("..."));//输入流
Object obj=ois.readObject();//从IO流中读取该Java对象,完成反序列化机制
2.对象引用的序列化
1)如果某个类的基本类型是另一个引用类型,那么这个引用类型必须是可序列化的,否则拥有该类型属性类是不可序列化的.
2) Java序列化算法:
所有保存到磁盘的对象都有一个序列化编号, 当程序试图序列化一个对象时,程序先检查该对象是否已经被序列化过了,只有当该对象从未被序列化过,系统才会将该对象转换成字节序列并输出. 如果某个对象已经被序列化过了,程序将只是输出一个序列化编号,而不是再次重新序列化该对象.
3)由于Java的序列化机制,当程序序列胡一个可变对象时,只有第一次使用writeObject方法输出时才会将该对象转换成字节序列并输出,即使后面该对象的属性已经改变,程序再次调用writeObject方法时,只是输出一个序列化编号,所以改变的属性值不会被输出.
三.自定义序列化
当对某个对象进行序列化时,系统会自动把该对象的所有属性依次进行序列化,如果某个属性引用到另一个对象,则被引用的对象也会被序列化,如果被引用的对象属性也引用了其他对象,则被引用的对象也会被序列化,这种被称为递归序列化.
1)被transient修饰的属性,Java对该属性不进行序列化. 被transient修饰的属性将被完全隔离在序列化机制外,在反序列化恢复该Java对象时无法获得该属性值.
transient关键字只能用于修饰属性,不可修饰Java中其他成分.
2)Java提供了另外一种序列化机制,通过这种自定义序列化机制可以让程序控制如何序列化各属性,甚至完全不序列化某些属性.
//上述代码为自定义序列化
writeObject方法负责写入特定类的实例状态, 以便相应的readObject可以恢复它. 通过重写该方法, 可以完全获得对序列化机制的控制.(可以自主决定哪些属性需要序列化,如何序列化)
readObject方法负责从流中读取并恢复对象属性, 通过重写该方法, 可以完全获得对反序列化机制的控制,(自主决定需要反序列化哪些属性,如何反序列化)
<注意>当序列化流不完整时, readObjectNoData 方法可以用来正确地初始化反序列化对象, 例如,当接收方使用的反序列化类的版本不同于发送方, 或者序列化流被篡改时, 系统都会调用 readObjectNoData 方法来初始化反序列化对象.
writeObject 方法存储属性的顺序应该和 readObject方法中恢复属性的顺序一致, 否则不能正常恢复该对象.
3)更彻底的序列化机制
如果需要在实现序列化某对象时替换该对象, 应为序列化类提供 Object writeReplace() 方法.
Java的序列化机制保证在序列化某个对象之前, 先调用该对象的 writeReplace()方法, 如果该方法返回另一个Java对象, 系统
将再次调用另一个对象的writeReplace方法, 直到该方法不再返回另一个对象为止. 程序最后将调用该对象的 writeObject 方法来保存该对象的状态.
//序列化机制在序列化Person对象时, 实际是转换为序列化 ArrayList对象
<注意>与writeReplace()方法相对的是, 序列化机制中还有一个特殊的方法, 可以实现保护性复制整个对象.
Object readResolve() 方法会紧接着readObject() 之后被调用, 该方法的返回值将会代替原来反序列化的对象, 而原来readObject 反序列化对象将会被立即丢弃.
四.使用 Externalizable 接口实现自定义序列化机制
Java提供了另一种序列化机制,这种序列化方式完全由程序员决定存储和恢复对象数据. 要实现该目标,Java类必须实现Externalizable接口.
1)Externalizable接口强制自定义序列化.
<重点>1.程序需要序列化实现Externalizable接口的对象, 一样调用ObjectOutputStream 的writeObject()方法输出该对象;反序列化该对象
,调用ObjectInputStream 的readObject()方法即可.
2.需要实现序列化的类,最好给出无参构造,否则程序会出现InvalidClassException异常.
五.两种序列化机制的对比
实现Serializable接口 实现Externalizable接口
系统自动存储必要信息 程序员决定存储哪些信息(自定义序列化)
Java内建支持,易于实现,只需实现该接口 提供两个空方法,实现该接口必须为两个空方法提供实现
即可,无序任何代码支持
性能略差 性能略高
<注意>虽然实现Externalizable接口能带来性能提升, 但由于该接口提升了编程的复杂度, 所以实际开发中大多使用Serializable接口来实现
序列化.
六.(重点)关于对象序列化的注意事项
1)对象的类名,属性(包括基本类型,数组,对其他对象的引用)都会被序列化; 方法, static属性(静态属性), transient属性(瞬态属性)
都不会被序列化.
2)实现Serializable接口的类如果要想让某个属性不被序列化, 可用transient修饰该属性, 而不是用static修饰该属性.(虽然static)
也能达到这样的效果, 但static不能这样用.
3)保证序列化对象的属性的类型也是可序列化的, 否则, 该类是不可序列化的.
4)反序列化对象时必须有对象的class文件.
5)当通过文件或者网络来读取序列化后对象时,必须按实际写入的顺序读取.
七.版本
反序列化对象时必须提供该对象的class文件, 有时随着项目的升级, 系统的class 文件也会升级, Java为了保证两个class文件的兼容性
Java序列化机制允许为序列化类提供一个private static final 的 serialVersionUID 属性值, 该属性值用于标识该Java类的序列化版本,
当一个类升级后, 只要它的serialVersionUID属性值保持不变, 序列化机制也会把它们当成同一个序列化版本.
<*>不显示定义serialVersionUID属性的另一个弊端是: 不利于在JVM之间的移植, 不同编译器计算该属性的计算策略可能不同, 即使该类
完全没有改变, 但因为JVM的不同, 也会出现序列化版本不兼容而无法正确反序列的现象.
<重点>对类的修改会导致该类反序列化失败时,应该为该类重新分配一个serialVersionUID属性值.
对类的哪些修改会导致反序列失败,分两种情况:
1)修改类时仅仅修改了方法, 静态属性或者瞬时属性, 则反序列化不受任何影响, 类定义无须修改serialVersionUID属性值.
2)如果修改了类中的非静态 , 非瞬态属性, 则可能导致序列化版本不兼容.