无法序列化已经序列化的类?

时间:2014-10-22 05:08:38

标签: java serialization

请参阅下面的剪辑

    USPresident usPresident = new USPresident(56);
    try (ObjectOutputStream oos = new ObjectOutputStream(new
    FileOutputStream("/home/nikhil/dev/USPresident.data"))){
    oos.writeObject(usPresident);
    usPresident.setTerm(57);
    oos.writeObject(usPresident);
    System.out.println("Serialized");
    }

创建了一个总统实例,任期为56。 序列化它。 将术语重置为57 再次序列化

但是,当我反序列化对象时,它的术语仍为56(而不是57!)

我在书中看到了这个解释,

  

是的,即使您使用它更改了术语,它也会打印56   setter到57并再次序列化。这是因为   serialVersionUID,由JVM在时间检查   序列化。如果一个类已经序列化并且您尝试了   再次序列化,JVM不会将其序列化。

但是,据我所知,serialVersionUID用于在反序列化期间检查Class对象是否与序列化对象匹配。此外,serialVersionUID用作Object签名的标识符,而不是状态。

不明白这里发生了什么。有人可以解释这种行为吗?

了解ObjectOutputStream的实现,这就是writeObjet的实现方式

public final void writeObject(Object obj) throws IOException {
    if (enableOverride) {
        writeObjectOverride(obj);
        return;
    }
    try {
        writeObject0(obj, false);
    } catch (IOException ex) {
        if (depth == 0) {
            writeFatalException(ex);
        }
        throw ex;
    }
}

查看布尔 enableOverride 。写'修改'对象必须如此。但是,只有在使用ObjectOutputStream的子类时,才能将其设置为true。请参阅下面的受保护构造函数,

protected ObjectOutputStream() throws IOException, SecurityException {
    SecurityManager sm = System.getSecurityManager();
    if (sm != null) {
        sm.checkPermission(SUBCLASS_IMPLEMENTATION_PERMISSION);
    }
    bout = null;
    handles = null;
    subs = null;
    enableOverride = true;
    debugInfoStack = null;
}

为什么这么奇怪的行为?

3 个答案:

答案 0 :(得分:6)

你的书是错误的,serialVersionUID是静态的,用于处理类本身的不同版本。

序列化的想法是获取一个活动对象(usPresident指向的对象)并将其所有状态保存为某种外部形式(字节,XML,JSON)。然后,您可以将该状态恢复为该对象的副本。这就像在文字处理器中保存文件一样。

但是,如果在序列化后修改对象,则没有连接。您已经将字节写入磁盘,并且它们不会更改,因为您在对象上调用方法。从这些字节重新创建对象时,它将具有与保存时相同的值。如果您忘记将更改保存到文字处理文档中,则磁盘上的文件仍具有旧内容。

在您的情况下,您在Java序列化中遇到了一个怪癖,因为您将同一个对象多次写入同一个ObjectOutputStream。为了能够序列化复杂的对象关系,Java序列化只保存一次对象,然后在再次保存时链接回它。关闭并重新打开流,您应该看到更新的值。

答案 1 :(得分:3)

  

为什么这么奇怪的行为?

保留对象标识。

将相同的对象放入集合中三次,序列化它,反序列化它,然后你仍然得到三次相同的对象(不只是三个具有相同状态的不同对象)。如果没有这个,你也可以不序列化自引用对象图(它会遇到无限循环)。

  

此外,serialVersionUID用作Object签名的标识符,而不是状态。

没错。你的这本书错了。 serialVersionUID与此处发生的事情无关。

  

但是,令我困惑的是第二次调用'writeObject'时。我希望它覆盖写入磁盘的数据。

无法以任何有效的方式完成。

您不能回到流中(例如,可能已经通过网络发送),并且序列化程序无法检测对象是否已更改(并且无条件地执行此操作会再次破坏使用循环对象的能力)图)。

FWIW,虽然您无法覆盖第一个对象,但您可以在编写之前通过调用oos.reset()强制流再次写入新副本。但是请注意,这会写出你在那里的所有对象的第二个副本,包括字符串等。

答案 2 :(得分:2)

为了将具有更新状态的对象(写入对象的第二个副本)重新写入流,您必须reset()流,以便从中删除对象的副本一组“已知对象”。可以这样做:

...
usPresident.setTerm(57);
oos.reset();  // <-- this guy!
oos.writeObject(usPresident);            

此外,当您正在阅读对象时 - 您必须阅读两个对象,第一个被保存的对象(状态为term == 56)和第二个(状态为term == 57):

ObjectInputStream in = new ObjectInputStream(
     new FileInputStream("/home/nikhil/dev/USPresident.data"));
usPresident = (USPresident)in.readObject();
System.out.println("usPresident.getTerm() = " +usPresident.getTerm()); // prints 56

usPresident = (USPresident)in.readObject();
System.out.println("usPresident.getTerm() = " +usPresident.getTerm()); // prints 57