序列化是否保留对象标识?

26

我使用Java Serializable接口和ObjectOutputStream来序列化对象(到目前为止,这种方法已经足够满足我的需求)。

我的API依赖于对象标识进行某些操作,我想知道序列化是否会保留它。也就是说:对于任意两个对象a和b,在序列化之前,如果a == b是否成立,则在反序列化之后是否仍然成立?

我找到了一些文字声称相反的情况 - 但他们要么写的是旧版本的JRE(我只关心1.6和可能的1.5),要么涉及RMI(这与我无关)。

文档没有提供关于对象标识的详细信息。sun.com上的技术文章提到ObjectOutputStream在对象上使用缓存,这对我来说只有在对象标识确实得到保留时才有意义,但我不太自信可以依赖这个脆弱的证据。

我已经尝试过(Java 1.6,OS X),发现是的对象的标识通过序列化保持不变。但我能从这些结果推断出结论吗,还是它们不可靠?

对于我的测试,我已经序列化了以下对象图:

C----------+
| b1    b2 |
+----------+
  |      |
  v      v
B---+  B---+
| a |  | a |
+---+  +---+
   \    /
    \  /
     \/
   A----+
   |    |
   +----+

最小化的重现代码:

import java.io.*;

public class SerializeTest {
    static class A implements Serializable {}

    static class B implements Serializable {
        final A a;

        public B(A a) {
            this.a = a;
        }
    }

    static class C implements Serializable {
        final B b1, b2;

        public C() {
            A object = new A();
            b1 = b2 = new B(object);
        }
    }

    public static void main(String[] args) throws IOException,
            ClassNotFoundException {
        C before = new C();
        System.out.print("Before: ");
        System.out.println(before.b1.a == before.b2.a);

        // Serialization.
        ByteArrayOutputStream data = new ByteArrayOutputStream();
        ObjectOutputStream out = new ObjectOutputStream(data);
        out.writeObject(before);
        out.close();

        // Deserialization.
        ObjectInputStream in =
            new ObjectInputStream(new ByteArrayInputStream(data.toByteArray()));
        C after = (C) in.readObject();
        System.out.print("After: ");
        System.out.println(after.b1.a == after.b2.a);
    }
}

如果a == b成立,那么它们不是任意对象。我认为你的问题涉及到引用。 - Thorbjørn Ravn Andersen
2个回答

20

对于任意两个对象a和b,如果在序列化之前a == b成立,那么只有在以下情况下,在反序列化后仍然成立:

  1. 同时将a和b作为同一流的一部分进行写入并随后从该流中读取。以下是ObjectInputStream文档中的引用:“使用引用共享机制正确恢复对象图形。”
  2. a和b的类没有覆盖可能更改引用恢复方式的readResolve()方法,也没有持有a和b的类。

对于所有其他情况,对象标识将不被保留。


2
你可能会想在你的答案中添加使用ObjectOutputStream类的writeUnshared()方法的后果。它最终会在流上创建新的唯一对象。更多信息请参阅Java对象序列化规范,网址为http://java.sun.com/j2se/1.4/pdf/serial-spec.pdf。 - Vineet Reynolds
谢谢,这正是我一直在寻找的。在我的情况下,我很幸运。不过,“引用共享”这个术语如果没有定义的话,还是比较晦涩的。@Vineet:感谢您提供规范链接。 - Konrad Rudolph

11
答案是,默认情况下,如果考虑到给定对象/图的两个分别序列化,那么对象的身份标识在序列化中是不会被保留的。例如,如果我通过 RMI 将一个对象从客户端发送到服务器并进行序列化,然后再进行另一次序列化(在单独的 RMI 调用中),则服务器上反序列化的 2 个对象将相等。
但是,在“单次序列化”中(例如,包含同一对象多次的图形式的单个客户端 - 服务器消息),在反序列化时身份标识保留。
对于第一种情况,您可以提供readResolve方法的实现,以确保返回正确的实例(例如在类型安全的枚举模式中)。 readResolve是一个私有方法,JVM将在反序列化Java对象时调用它,使对象有机会返回不同的实例。例如,在 enum 添加到语言之前,可能就是这样实现的TimeUnitenum
public class TimeUnit extends Serializable {

    private int id;
    public TimeUnit(int i) { id = i; }
    public static TimeUnit SECONDS = new TimeUnit(0);

    //Implement method and return the relevant static Instance
    private Object readResolve() throws ObjectStreamException {
        if (id == 0) return SECONDS;
        else return this;
    }
}

.


(直接翻译为“点”可能不够准确,因此保留原文)

1
谢谢。在我的情况下,使用readResolve正是我想要避免的,因为会使记录变得比类型安全枚举模式更加复杂。幸运的是,我只对单个序列化感兴趣。 - Konrad Rudolph

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接