Java 提供了一种对象序列化的机制,该机制中,一个对象可以被表示为一个字节序列,写出到输出流中,并在之后将其读回。整个过程都是 Java 虚拟机独立的。
1 保存和加载序列化对象
一个类的对象要想序列化成功,必须满足两个条件:
- 该类必须实现 java.io.Serializable 接口。
- 该类的所有属性必须是可序列化的。如果有一个属性不是可序列化的,则该属性必须注明是短暂的。
只有需要写出对象时才使用 witeObject/readObject,对于基本类型值,使用诸如 writeInt/readInt 或 writeDouble/readDouble (对象流类都实现了 DataInput/DataOutput 接口)
1.1 序列化对象
- 对每一个对象引用都关联一个序列号(serial number)。
- 对每个对象,当第一次遇到时,保存其对象数据到输出流中。
- 如果某个对象之前已经被保存过,那么只写出“与之前保存过的序列号为x的对象相同”。
ObjectOutputStream 类 writeObject(Object x) 方法:序列化一个对象,并将它发送到输出流。
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("employee.txt"));
Manager boss = new Manager("Carl Cracker",8000,1987,12,25);
out.writeObject(harry);
1.2 反序列化对象
- 对于对象输入流中的对象,在第一次遇到其序列号时,构建它,并使用流中数据来初始化它,然后记录这个顺序号和新对象之间的关联。
- 当遇到“与之前保存过的序列号为x的对象相同”标记时,获取与这个顺序号相关联的对象引用。
ObjectInputStream 类 readObject() 方法:从流中取出下一个对象,并将对象反序列化。
ObjectInputStream in = new ObjectInputStream(new FileInputStream("employee.txt"));
Employee e1 = (Employee) in.readObject();
2 对象序列化的文件格式
对象序列化是以特殊的文件格式存储对象数据的
- 对象流输出中包含所有对象的类型和数据域。
- 每个对象都被赋予一个序列号。
- 相同对象的重复出现将被存储为对这个对象的序列号的引用。
示例:
3 修改默认的序列化机制
3.1 避免序列化
不可序列化的类和不需要序列化的域,需要标记成是 transient 的。瞬时的域在序列化时会被跳过。
3.2 手动序列化
可序列化的类可以定义具有下列签名的方法,之后数据域就再不会被自动序列化,取而代之的是调用这些方法。
private void readObject(ObjectInputStream in) throws IOException,ClassNoutFoundException;
private void writeObject(ObjectOutputStream out) throws IOException;
3.3 扩展序列化
除了让序列化机制来保存和恢复对象数据,类还可以定义它自己的机制。为了做到这一点,这个类必须实现 Externalizable 接口,这需要定义两个方法:
public void readExternal(ObjectInputStream in) throws IOException ,ClassNotFoundException;
Public void writeExternal(ObjectOutputStream out) throws IOException;
4 序列化单例和类型安全的枚举
单例的类可以用==
操作符来测试对象的等同性。但及时构造器私有,序列化机制也可以创建新的对象!这时的新对象通过==
就不相等了。
为了解决这个问题,需要定义另外一种称为 readResolve 的特殊序列化方法。在这个方法中检查 value 并返回合适的单例对象。
5 版本管理
一个类的所有较新的版本都必须把 serialVersionUID 常量定义为与最初版本的指纹相同,这样序列化系统就可以读入这个类的对象的不同版本。
如果这个类只有方法产生了变化,那么在读入新对象数据时是不会有任何问题的。但是,如果数据域产生了变化,那么就可能会有问题。
对象输入流会将这个类当前版本的数据域与被序列化的版本中的数据域进行比较(只考虑非瞬时和非静态的数据域)。
- 如果这两部分数据域之间名字匹配而类型不匹配,那么对象输入流不会尝试将一种类型转换成另一种类型,因为这两个对象不兼容;
- 如果被序列化的对象具有在当前版本中所没有的数据域,那么对象输入流会忽略这些额外的数据;
- 如果当前版本具有在被序列化的对象中所没有的数据域,那么这些新添加的域将被设置成它们的默认值(如果是对象则是null,如果是数字则为0,如果是boolean值则是false)。
6 为克隆使用序列化
将对象序列化到输出流中,然后将其读回。这样产生的新对象是对现有对象的一个深拷贝。在此过程中,我们不必将对象写出到文件中,因为可以用 ByteArrayOutputStream 将数据保存到字节数组中。
要想得到 clone 方法,只需扩展 SerialCloneable 类。
我们应该当心这个方法,通常会比显式地构建新对象并复制或克隆数据域的克隆方法慢得多。