0%

序列化Serializable与readResolve()方法

以一个简陋的单例模式举例:

1
2
3
4
5
6
7
8
public class SerializableTest implements Serializable {

public static final SerializableTest t = new SerializableTest();

private SerializableTest() {

}
}

将构造器设置为 private 权限,使用 static 和 final,这样运行中获得的实例都应是同一个对象,但是使用序列化/反序列话时,这将被打破。如下:

1
2
3
4
5
6
7
ObjectOutputStream oou = new ObjectOutputStream(new FileOutputStream("serializable.a"));
oou.writeObject(new SerializableTest());
oou.close();

ObjectInputStream oin = new ObjectInputStream(new FileInputStream("serializable.a"));
SerializableTest t1 = (SerializableTest) oin.readObject();
System.out.println(t == t1);

此时将输出false,也就是说反序列化读入的对象与原对象不是同一个。

为了解决这个问题,我们在 SerializableTest 类中添加一个 readResolve() 方法,返回我们希望的对象,如下:

1
2
3
protected Object readResolve() throws ObjectStreamException {
return t;
}

我们在 readResolve() 方法返回我们希望的实例,这样再次运行,将输出true,反序列化得到的是原来的对象。

readResolve() 不是重写的父类方法,也不是实现的接口的方法,不难想到这是用反射实现。

debug一下可以追踪到,在 ObjectInputStream 类的 readOrdinaryObject(boolean unshared) 方法有以下代码:

1
2
3
4
5
6
if (obj != null &&
handles.lookupException(passHandle) == null &&
desc.hasReadResolveMethod())
{
Object rep = desc.invokeReadResolve(obj);
......

再继续,可以看到 invokeReadResolve(Object obj) 方法有以下代码:

1
2
3
4
5
requireInitialized();
if (readResolveMethod != null) {
try {
return readResolveMethod.invoke(obj, (Object[]) null);
......

也就是说,以上的“单例模式”,以及常用的双重校验锁等,在反序列化的情况下会构建出新的对象,添加 readResolve() 方法可以在反序列化时保持单例。