请参阅下面的剪辑
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;
}
为什么这么奇怪的行为?
答案 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