Java:何时在序列化期间添加readObjectNoData()?

我正在阅读“Effective Java”中的序列化章节。 我试图理解书中的下一段。

如果您实现的类具有可序列化和可扩展的实例字段,那么您应该注意这一点。 如果类的实例字段初始化为默认值(整数类型为零,布尔值为false,对象引用类型为null),则会违反不变量,必须将此readObjectNoData方法添加到类中:

// readObjectNoData for stateful extendable serializable classes private void readObjectNoData() throws InvalidObjectException { throw new InvalidObjectException("Stream data required"); } 

我不确定上述陈述的含义。

为了测试这个,我创建了一个Person类(可序列化和可扩展)

  class Person implements Serializable{ private String name; private int age; Person() { this("default",1); } Person(String name, int y) { this.name = name; this.age = y; } } 

以及扩展它的类Employee。

  class Employee extends Person { String address ; public Employee() { super(); address ="default_address"; } public Employee(String name , int age, String address) { super(name,age); this.address = address; } } 

我创建的Person类中是否有任何不变量? 什么时候会被侵犯? 我复制粘贴了Employee类中的readObjectData()方法的代码,但它从未被调用过。 何时调用readObject()方法? 我错过了什么吗?

Java对象序列化规范中的readObjectNoData部分看起来很有趣(见下文)。

您对该问题的编辑提供了一个完美的例子。 如果Employee在没有扩展Person时被serialized ,并且稍后deserialized ,那么Person部分将被初始化为空字符串和0年龄。 使用此方法,您可以将它们分别初始化为“name”和1。

对于可序列化对象,readObjectNoData方法允许类在子类实例反序列化的情况下控制其自己字段的初始化,并且序列化流不会将所讨论的类列为反序列化对象的超类。 如果接收方使用与发送方不同版本的反序列化实例的类,并且接收方的版本扩展了未由发送方版本扩展的类,则可能发生这种情况。 如果序列化流已被篡改,也可能发生这种情况; 因此,尽管存在“恶意”或不完整的源流,readObjectNoData对于正确初始化反序列化对象非常有用。

private void readObjectNoData() throws ObjectStreamException;

每个可序列化的类可以定义自己的readObjectNoData方法。 如果可序列化类没有定义readObjectNoData方法,那么在上面列出的情况下,类的字段将被初始化为它们的默认值(如JavaTM语言规范第二版第4.5.5节所列); 当引入对readObjectNoData方法的支持时,此行为与JavaTM 2 SDK,Standard Edition 1.4版之前的ObjectInputStream的行为一致。 如果可序列化的类确实定义了一个readObjectNoData方法并且出现了上述条件,则在反序列化期间将调用readObjectNoData,否则将调用类定义的readObject方法,如果该类已将该类列为流的超类正在反序列化的实例。

我创建的Person类中是否有任何不变量? 什么时候会被侵犯?

没有显式,但想象一下,类中的其他方法假定name永远不会为null ,如果有的话会抛出NullPointerException 。 在这种情况下, name的非null值是不变的。

我在Employee类中复制了readObjectData()方法的代码,但它从未被调用过。 何时调用readObject()方法?

没有方法readObjectData()涉及序列化,这必须是一个错字。 每次反序列化序列化对象时都会调用readObject()方法。

当反序列化包含该方法的类的子类时,对于某些模糊的角点案例,会readObjectNoData()方法。

Sun Oracle网站上的高级序列化文章介绍了这些序列化帮助程序方法的用途。 我建议你从那里开始并发布你可能遇到的任何后续问题。

(更新)

如果您感到好奇,则在1.4版中添加了readObjectNoData方法,以涵盖涉及向现有可序列化类添加可序列化超类的极端情况。 详细信息可以在序列化规范Serialization,3.5中找到 。

引用的文本是:

对于可序列化对象,readObjectNoData方法允许类在子类实例反序列化的情况下控制其自己字段的初始化,并且序列化流不会将所讨论的类列为反序列化对象的超类。 如果接收方使用与发送方不同版本的反序列化实例的类,并且接收方的版本扩展了未由发送方版本扩展的类,则可能发生这种情况。 如果序列化流已被篡改,也可能发生这种情况; 因此,尽管存在“恶意”或不完整的源流,readObjectNoData对于正确初始化反序列化对象非常有用。

所以这可能发生在两种情况:

  • 解码对象流的JVM有一个更新版本的子类被反序列化( Employee ),它扩展了一些父类( Person )。 最初编码对象流的JVM具有这些类的不同旧版本,其中Person还不是Employee的超类。
  • 有人故意弄乱对象流以破坏事物。

“可扩展”意味着“可以有一个子类”。

readObjectNoData用于一种不常见的情况,其中序列化程序(编写器)使用的是没有基类的类的版本,而类的反序列化程序(阅读器)具有基于子类的类的版本。 通过实现readObjectNoData,子类可以说“如果我的基类不在序列化数据中就可以了 – 只需使其为空”。 请参阅这些发行说明 。

Person类中的不变量可能类似于:

 age must be greater than 0 

因此,当使用默认值0(参见此处: http : //download.oracle.com/javase/tutorial/java/nutsandbolts/datatypes.html )实例化Person类时,您将违反该不变量。

但是,给定默认构造函数你会没事的:)