Java序列化机制拾遗

来源:互联网 发布:顾家北网络课程 编辑:程序博客网 时间:2024/04/28 23:05

对象序列化的概念加入到语言中是为了支持两种主要特性:一是Java的远程方法调用(RMI),使存活于其他计算机上的对象使用起来就像是存活在本机上一样。当向远程对象发送消息时,需要通过对象序列化来传输参数和返回值。二是,对于JavaBean,对象的序列化也是必需的。使用一个Bean时,一般情况下是在设计阶段对他的状态信息进行配置,这种状态信息必须保存下来,并在程序启动时进行后期恢复;这种具体工作就是由对象序列化完成的。

对象序列化机制允许把内存中的Java对象转换为平台无关的二进制流,其他程序一旦获得了这种二进制流就可以将二进制

流恢复成原来的Java对象。序列化机制使得对象可以脱离程序的运行而独立存在。对象的序列化是指将Java对象写入IO流中,与此对应的,对象的反序列化则指从IO流中恢复该Java对象。

Java序列化可以实现对象的深复制,但是对象对应的类的各个成员必须是可序列化的,否则会抛出异常。

只有实现了Serializable接口或者Externalizable接口的类才是可序列化的。

1、对象的序列化以及反序列化

对象的序列化的步骤:

(1)、创建一个ObjectOutputStream, 这个输出流是一个处理流,所以必须建立在其他节点流的基础上。

ObjectOutputStream oos=newObjectOutputStream(new FileOutputStream(“object.txt”));

(2)、调用ObjectOutputStream对象的writeObject()方法输出可序列化对象,

Person p=new Person();oos.writeObject(p);

反序列化的步骤:

(1)、创建一个ObjectInputStream输入流,这个输入流也是一个处理流,所以也要建立在其他节点流的基础之上

ObjectInputStream ois=newObjectInputStream(new FileInputStream(“object.txt”));

(2)、调用ObjectInputStream对象的readObject()方法读取输入流中的对象,该方法返回一个Object类型的Java对象,如果程序知道该Java对象的类型,则可以将该对象强制类型转换成其真实的类型。

 Person p=(Person)ois.readObject();

注意:1、反序列化读取的仅仅是Java对象的数据,而不是Java类,因此采用反序列化恢复Java对象时,必须提供该Java对象所属类的class文件,否则将会引发ClassNotFoundException异常。

2、对于实现Serializable接口的序列化类,在反序列化的时候不会调用其构造器;但是对于实现Externalizable接口的序列化类来说,反序列化的时候则会在调用readExternal(ObjectInput in)方法之前,会调用该类的无参构造器,因此,对于实现Externalizable接口的序列化类来说,必须要有无参构造器。

2、对象引用的序列化

如果某个类的Field类型不是基本类型或String类型,而是另一个引用类型,那么这个引用类必须是可序列化的,否则拥有该类型的Field的类也是不可序列化的。

       Java序列化机制采用的序列化算法:

(1) 所有保存到磁盘中的对象都有一个序列化编号;

(2) 当程序试图序列化一个对象时,程序将先检查该对象是否已经被序列化过,只有该对象从未在本次虚拟机中被序列化过,系统才会将该对象转换成字节序列并输出;

(3) 如果某个对象已经序列化过,程序将只是直接输出一个序列化编号,而不是再次重新序列化该对象。

这种算法会引起一个问题:当程序使用writeObject()方法后改变了该对象的Field值,再次调用writeObject()方法,改变的Field值也不会被输出的。

3、自定义序列化

为了某些特殊的需求,比如序列化的时候防止某些敏感字段一同序列化,可以采取自定义序列化的方式。对于敏感字段,比较简单的方式是使用transient关键字修饰相应的Field。

使用transient关键字修饰Field虽然简单,但被transient修饰的Field将被完全隔离在序列化机制之外,导致在反序列化恢复Java对象时无法取得该Field值。Java还提供了一种自定义序列化机制,通过这种自定义序列化机制可以让程序控制如何序列化各Field,甚至完全不序列化某些Field。

在序列化和反序列化过程中需要特殊处理的类(这个类应该是实现了Serializable的类,而不是实现了Externalizable接口的类)应该提供如下特殊签名的方法,这些特殊的方法用以实现自定义序列化。

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

writeObject()方法负责写入特定类的实例状态,默认情况下会调用out.defaultWriteObject来保存Java对象的各Field

readObject()方法负责从流中读取并恢复对象Field,默认会调用in.defaultReadObject来恢复Java对象的非静态和非瞬态Field。

当序列化流不完整时,readObjectNoData方法可以用来正确地初始化反序列化的对象

4、更彻底的序列化机制

还有一种更彻底的自定义机制,如果需要实现序列化某个对象时替换该对象,则应为序列化类提供如下特殊方法:

ANY-ACCESS-MODIFIER Object writeReplace() throws ObjectStreamException;

系统在序列化某个对象之前,会先调用该对象的writeReplace和writeObject两个方法,系统总是先调用被序列化对象的writeReplace方法,如果该方法返回另一个对象,系统将再次调用另一个对象的writeReplace方法,直到不再返回另一个对象为止,程序最后将调用该对象的writeObject方法来保存该对象的状态。

序列化机制里有一个特殊的方法,它可以实现保护性复制整个对象。
ANY-ACCESS-MODIFIER Object readResolve() throws ObjectStreamException;
这个方法会紧接着readObject()之后被调用,该方法的返回值将会代替原来反序列化的对象,而原来反序列化的对象将会被立即丢弃。在序列化单例类尤其有用。
import java.io.*;class SingleTon implements Serializable{private static final SingleTon instance=new SingleTon();private SingleTon(){}public static SingleTon getInstance(){return instance;}private Object readResolve() throws ObjectStreamException{return instance;}}public class SingleTonSerializeTest {/** * @param args */public static void main(String[] args) {// TODO Auto-generated method stubtry(ObjectOutputStream oos=new ObjectOutputStream(new FileOutputStream("object.txt"));ObjectInputStream ois=new ObjectInputStream(new FileInputStream("object.txt"))){SingleTon single1=SingleTon.getInstance();oos.writeObject(single1);SingleTon single=(SingleTon) ois.readObject();System.out.println(single1==SingleTon.getInstance());System.out.println(single==single1);System.out.println(single==SingleTon.getInstance());}catch(IOException | ClassNotFoundException e){e.printStackTrace();}}}

5、实现了Externalizable接口的序列化实现方式

另一种自定义序列化机制,为实现Externalizable接口,这个接口里有两个方法:

(1) void readExternal(ObjectInput in):需要序列化的类实现readExternal()方法来实现反序列化。该方法调用DataInput(ObjectInput的父接口)的方法readXxx()来恢复基本类型的Field值,调用ObjectInput的readObject()方法来恢复引用类型的Field值。

(2) void writeExternal(ObjectOutput out):需要序列化的类实现writeExternal()方法来保存对象的状态。该方法调用DataInput(ObjectInput的父接口)的方法writeXxx()来保存基本类型的Field值,调用ObjectOutput的writeObject()方法来保存引用类型的Field值。

6、static和transient修饰的Field的序列化问题
static和transient修饰的Field不会被序列化,如果想要实现static Field的序列化,则需要使用如下的方法:

public static void serializeStaticState(ObjectOutputStream os) throws IOException;public static void deserializeStaticState(ObjectInputStream os) throws IOException;


本文整理自李刚老师编著的《疯狂Java》以及参考《Thinking in Java》