Java序列化笔记,摘自《疯狂Java讲义》

Java 序列化

1.序列化的含义和意义

对象序列化指将一个Java对象写入IO流中。
对象支持序列化则必须让它的类是可序列化的。该类必须实现如下两个接口之一

  • Serializable
  • Externalizable

建议:JavaBean类都实现Serializable。

2.使用对象流实现序列化

  • 反序列化读取的仅仅是Java对象的数据,而不是Java类,因此采用反序列化恢复Java对象时,必须提供该Java对象所属类的class文件,否则将会引发ClassNotFoundException异常。
  • 反序列化无需通过构造器来初始化Java对象。
  • 如果使用序列化机制向文件中写入了多个Java对象,使用反序列化机制恢复对象时必须按实际写入顺序读取。
  • 当一个可序列化类有多个父类时,这些父类要么有无参数构造器,要么也是可序列化的,否则抛出InvalidClassException。
  • 如果父类时不可序列化的,只带有无参数构造器,则该父类中定义的成员变量值不会序列化到二进制流中。(反序列化时会调用父类的无参构造器,重新实例化父类对象)

3.对象引用的序列化

Java序列化机制算法:

  • 所有保存到磁盘中的对象都有一个序列化编号。
  • 当程序试图序列化一个对象时,程序将先检查该对象是否已经被序列化过,只有该对象从未被序列化过,系统才会将该对象转换成字节序列并输出。
  • 如果某个对象已经序列化过,程序将直接输出一个序列化编号,而不是再次重新序列化该对象。

注:当程序序列化一个可变对象时,只有第一次writeObject()方法输出时才会将该对象转换成字节序列并输出,当再次调用writeObject()方法时,程序只是输出前面的序列化编号,即使后面该对象的实例变量值已被改变,改变的实例变量值也不会被输出。

4.自定义序列化

当对某个对象进行序列化时,系统会自动把该对象的所有实例变量依次进行序列化,如果某个实例变量引用另一个对象,则被引用的对象也会被序列化;如果被引用的对象实例变量也引用了其他对象,则被引用的对象也会被序列化,这种情况被称为递归序列化。

4.1 transient关键字

在实例变量前使用transient关键字修饰,可以指定Java序列化时无需理会该实例变量。
transient关键字只能用于修饰实例变量,不能用于修饰Java程序中的其他成分。
transient关键字修饰实例变量将被完全隔离在序列化机制之外,这样导致在反序列化恢复Java对象时无法取得该实例的变量值。

4.2 自定义序列化

在类中提供如下方法,这些方法用以实现自定义序列化。

  • private void writeObject(java.io.ObjectOutputStream out)throws IOException
  • private void readObject(java.io.ObjectInputStream in)throws IOException,ClassNotFoundException
  • private void readObjectNoData()throws ObjectStreamException

注:writeObject的顺序与readObject的顺序一致,否则不能正常恢复Java对象。

writeReplace方法由序列化机制调用,只要该方法存在。在序列化某个对象前,先调用该对象writeReplace()方法,如果该方法返回另一个Java对象,则系统转化序列化另一个对象。
与writeReplace方法相对的是,readResolve(),这个方法在readObject()后被调用,该方法的返回值将代替原来反序列化的对象,原来的对象被丢弃。
readResolve()在序列化单例类和枚举类时尤其有用。所有的单例类和枚举类在实现序列化时都应该提供readResolve方法,这样才能保证反序列化的正常。

5.自定义序列化机制

Java除了Serializable,还提供了另一种序列化机制,这种序列化方式完全由程序员决定存储和恢复对象数据。要实现该目标,Java必须实现Externalizable接口,接口中方法如下:

  • void readExternal(ObjectInput in):需要实现readExternal方法实现反序列化。
  • void writeExternal(ObjectOutput out):需要实现writeExternal方法来保存对象状态。

两种序列化机制的对比:

Serializable

  • 系统自动保存必要信息
  • Java内建支持,易于实现,只需实现该接口即可,无需任何代码支持(也可以自定义)
  • 性能略差

Externalizable

  • 程序员决定存储哪些信息
  • 仅仅提供空方法,必须自定义实现序列化
  • 性能略高

注意

  • 对象的类名、属性(基本类型、数组、对其他对象的引用)都会被序列化;方法、static属性、transient属性都不会被序列化。
  • 实现Serializable接口的类如果需要想让某个属性不被序列化,可在属性前加transient修饰符,而不是加static。
  • 保证序列化对象的属性的类型也是可序列化的,否则需要使用transient关键字来修饰该属性,要不然,则该类是不可序列化的。
  • 反序列化对象时必须有序列化对象的class文件。

6.版本

反序列化Java对象时必须提供该对象的class文件,如果项目升级,Java如何保证两个class文件的兼容性?

Java序列化机制允许为序列化类提供一个private static final的serialVersionUID属性值,该属性值用于表示该Java类的序列化版本。

  • 如果修改类时仅仅修改了方法,则反序列化完全不受任何影响,类定义无需修改serialVersionUID的属性值。
  • 如果修饰类时仅仅修改了静态属性或瞬态属性,则反序化不受任何影响,类定义无需修改serialVersionUID属性值。
  • 如果修改类时修饰了非静态、非瞬态属性,则可能导致序列化版本不兼容,如果对象流中的对象和新类中包含同名的属性,而属性类型不同,则反序列化失败,类定义应该更新serialVersionUID属性值。如果对象流中对象比新类中包含更多的属性,则多处的属性值被忽略,序列化版本可以兼容,类定义可以不更新serialVersionUID属性值;但反序列化得到的新对象中多处的属性值都是null(引用类型属性)或0(基本类型属性)。

引用

疯狂Java讲义