Java序列化及反序列化

1.概述

     要实现序列化,则必须实现serializable或Externalizable接口。后者继承自前者,两者的区别:实现前者的类可以采用默认的序列化方式。而实现后者的类则完全由自身来控制序列货摊行为。
     在序列化和反序列化过程中需要特殊处理的类必须使用下列准确签名来实现特殊方法: 

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

  
     writeObject 方法负责写入特定类的对象的状态,以便相应的 readObject 方法可以还原它。通过调用 out.defaultWriteObject 可以调用保存 Object 的字段的默认机制。该方法本身不需要涉及属于其超类或子类的状态。状态是通过使用 writeObject 方法或使用 DataOutput 支持的用于基本数据类型的方法将各个字段写入 ObjectOutputStream 来保存的。
      readObject 方法负责从流中读取并还原类字段。它可以调用 in.defaultReadObject 来调用默认机制,以还原对象的非静态和非瞬态字段。defaultReadObject 方法使用流中的信息来分配流中通过当前对象中相应命名字段保存的对象的字段。这用于处理类发展后需要添加新字段的情形。该方法本身不需要涉及属于其超类或子类的状态。状态是通过使用 writeObject 方法或使用 DataOutput 支持的用于基本数据类型的方法将各个字段写入 ObjectOutputStream 来保存的。 
      假定有一个名为Customer的类,它的对象需要序列化,如果Customer类仅仅实现了Serializable接口的类,那么将按照以下方式序列化及反序列化Customer对象:

  • ObjectOutputStream采用默认的序列化方式,对Customer对象的非transient的实例变量进行序列化。
  • ObjectInputStream采用默认的反序列化方式,对Customer对象的非transient的实例变量进行反序列化。

     如果Customer类仅仅实现了Serializable接口的类,并且还定义了readObject(ObjectInputStream in)和writeObject(ObjectOutputStream out)方法,那么将按照以下方式序列化及反序化Customer对象:

  • ObjectOutputStream会调用Customer类的writeObject(ObjectOutputStream out)方法来进行序列化。
  • ObjectInputStream会调用Customer类的readObject(ObjectInputStream in)方法来进行反序列化。

     如果Customer类实现了Externalizable接口,那么Customer类必须实现readExternal
     (ObjectInput in)和writeExternal(ObjectOutput out)方法。在这种情况下,将按照以下方式序列化及反序列化Customer对象:

  • ObjectOutputStream会调用Customer类的writeExternal(ObjectOutput out)方法来进行序列化。
  •  ObjectInputStream先通过Customer类的不带参数的构造方法创建一个Customer对象,然后调用它的readExternal(ObjectInput int)方法来进行反序列化。

2.实现Serializable接口
      ObjectOutputStream只能对实现了Serializable接口的类的对象进行序列化。默认情况下,ObjectOutputStream按照默认方式序列化,这种序列化方式仅仅对对象的非transient的实例变量进行序列化,而不会序列化对象的transient的实例变量,也不会序列化静态变量。

      而当ObjectInputStream按照默认方式反序列化时,有以下特点:

  • 如果在内在中对象所属的类还没有被加载,那么会先加载并初始化这个类。如果在classpath中不存在相应的类文件,那么会抛出ClassNotFoundException。
  • 在反序列化时不会调用类的任何构造方法。

     被transient修饰符来修饰的实例变量是不会序列化的,一般用transient来修饰以下类型的的变量:

    1) 实例变量不代表对象的固有的内部数据,仅仅代表具有一定逻辑含义的临时数据。
    2) 实例变量表示一些比较敏感的信息,如密码,出于安全方面的原因,不希望对其序列化。
    3) 实例变量需要按照用户自定义的方式序列化,如经过加密后再序列化。这这种情况下可以将其用transient修饰,然后在writeObject()方法中对其序列化。

   

2.1序列化对象图

    参见SerializableDemo中的Customer2及Order2类之间的关系。
    当通过ObjectOutputStream对象的writeObject(customer)方法序列化Customer2对象时,也会序列化与它关联的Order2对象。而当通过ObjectInputStream对象的readObject()方法反序列化Customer2对象时,实际上会对整个对象图反序列化。
    如果对象A持有对象B的引用(注意是A持有B的引用),以及间接持有其他对象的引用,则按照默认方式序列化对象A时,会将A以及A持有的以及间接持有的所有对象都序列化。反序列化也是如此。

   

2.2控制序列化的行为

    如果用户希望控制类的序列化方式,可以在可序列化类中提供以下形式的writeObject()方法和readObject()方法:

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

 

    一般的做法是,在writeObject()方法中,选留骼ObjectOutputStream的defaultWriteObject()方法,使得对象输出流先执行默认的序列化操作。
    反序列化时,在readObject()方法中,先调用ObjectInputStream的defaultReadObject()方法。

   

2.3readResolve()方法在童便类中的运用
    如果一个类提供了readResolve()方法,那么在执行反序列化操作时,先按照默认方式或者用户自定义的方式进行反序列化,最后再调用readResolve()方法,该方法返回的对象为反序列化的最终结果。
    readResolve()方法应该能够被类本身、同一个包中的类,或者访问,因此readResolve()方法的访问权限可以是pirvate、默认或protected级别。
    readResolve()方法用来重新指定反序列化得到的对象,与此对应,Java序列化规范还允许在可序列化类中定义一个writeReplace()方法,用来重新指定被序列化的对象。writeReplace()方法返回一个Object类型的对象,这个返回对象才是真正要被序列化的对象。权限也可以为以上三种之一。
  

3.实现Externalizalbe接口
    Externallizable接口继承自Serializable接口,如果一个类实现了Externalizable接口,那么将完全由这个类控制自身的序列化行为。Externalizable接口中声明了两个方法:

public void writeExternal(ObjectOutput out)throws IOException
public void readExternal(ObjectInput in) throws IOException,ClassNotFoundException

    writeExternal负责序列化操作,readExternal负责反序列化操作。在对实现了Externallizable接口的类的对象进行反序列化时,会先调用类的不带参数的构造方法,这是有别默认反序列化方式的(见2)。所以实现Externalizable接口的类必须要有不含参数的构造方法。

 

4.可序列化类的不同版本的序列化兼容性

    实例见SerializableDemo里的Customer5的两个版本。将Customer1.0和SimpleServer放在server端,将Customer2.0和SimpleClient放在Client端,直接运行,将发现抛出错误,显示不兼容,解决办法是手动将两个Customer的serialVersionUID设为同一个值,这样就能兼容。但是这种办法的能力很有限,当一个类的不同版本的serialVersionUID相同,仍然有可能出现序列化不兼容的情况。因为序列化兼容性不仅取决于serialVersionUID,还取决于类的不同版本的实现细节和序列化细节。

你可能感兴趣的:(java)