Java 序列化机制

内存对象输出外界

序列化接口


java.io.Serializable是一个标识性接口,不包含任何方法

Serializable.java
1
2
public interface Serializable {
}

Serializable提供了一种自动化的解决方案,无需具体实现,非transient非static的成员都是序列化的目标,即便是private也可以

默认流程


序列化和反序列化本质就对象通过ObjectOutputStreamObjectInputStream进行输入输出

Sample
1
2
3
4
ByteArrayOutputStream bos = new ByteArrayOutputStream();
new ObjectOutputStream(bos).writeObject(obj);
ByteArrayInputStream bin = new ByteArrayInputStream(bos.toByteArray());
obj = new ObjectInputStream(bin).readObject();

或者文件保存

Sample
1
2
3
4
FileOutputStream fos = new FileOutputStream("file");
new ObjectOutputStream(fos).writeObject(obj);
FileInputStream fin = new FileInputStream("file");
obj = new ObjectInputStream(fin).readObject();

自定义序列化


虽然是自动行为,但也提供了一组方法来定制,比较特殊的是这些方法都是private的,ObjectOutputStreamObjectInputStream会检测用户是否定义了这些钩子方法,进而调用

1
2
3
4
5
6
7
8
9
10
11
//定义怎么输出
private void writeObject(ObjectOutputStream out) throws IOException
//定义怎么读入
private Object readObject(ObjectInputStream in) throws IOException, ClassNotFoundException;
//发生异常时执行,比如版本不兼容,数据不完整等
private void readObjectNoData() throws ObjectStreamException;

//定义读到后怎么后处理,可以转换成其他对象作为序列化结果
private Object readResolve() throws ObjectStreamException
//定义输出前怎么预处理,可以转换其他对象作为序列化目标
private Object writeReplace() throws ObjectStreamException

通常自定义不会彻底重写,而是在默认逻辑的基础上加上定制逻辑
out.defaultWriteObject()in.defaultReadObject()提供了默认功能

序列化还有一个子接口Externalizable,可以完全控制过程,脱离自动机制纯手动维护

Externalizable.java
1
2
3
4
public interface Externalizable extends java.io.Serializable {
void writeExternal(ObjectOutput out) throws IOException;
void readExternal(ObjectInput in) throws IOException, ClassNotFoundException;
}

由于序列化会暴露内部成员的值,因此为了安全性序列化可能会有加解密过程
序列化还提供了回调接口用于在反序列化的最后进行验证

ObjectInputValidation.java
1
2
3
public interface ObjectInputValidation {
public void validateObject() throws InvalidObjectException;
}

调用顺序

  1. writeReplace
  2. writeObject
  3. readObject
  4. readResolve
  5. validateObject

序列化版本控制


实际应用中,序列化类可能发生变化

序列化机制有一定兼容性,反序列化时

  • 如果类成员多余,直接忽略
  • 如果类成员缺少,使用默认值

序列化类需要定义版本号来表示是否兼容,只有版本号一致的才能进行

Sample
1
private static final long serialVersionUID = 1L;

这个版本号可以用IDE自动生成,会根据当前类成员情况计算一个哈希值

序列化与类构造


  • 无继承的类反序列化不通过构造函数,所以有没有无参构造都无所谓
  • 一个类可以序列化,那么它的类成员都必须能够序列化,否则报错
  • 父类实现了序列化,子类都具备序列化能力
  • 子类实现了序列化,父类没实现,那么父类一定要有无参构造函数。反序列化时首先要调用父类无参构造函数,这时父类的值全是默认值,如果需要同时反序列化父类的值,那么这些工作都需要由子类负责。
  • 序列化效果等同于另一种途径的构造,可以作为深度拷贝的手段,有些场景需要保护,比如不可变、单例等等