Java: When to add readObjectNoData() during serial

2019-03-12 03:36发布

问题:

I am reading the serialization chapter in "Effective Java". I am trying to understand the below paragraph in the book.

If you implement a class with instance fields that is serializable and extendable,there is a caution you should be aware of. If the class has invariants that would be violated if its instance fields were initialized to their default values (zero for integral types, false for boolean, and null for object reference types), you must add this readObjectNoData method to the class:

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

I am not sure what the above statment means .

To test this, I created a class Person (both serializable and extendable)

   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;
      }
    }

and a class Employee which extends it.

     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;
    }
           }

Are there any invariants in the Person class I created ? When will they be violated ? I copy pasted the code for readObjectData() method in the Employee class , but it never got called. When will the method readObject() be called ? Am I missing something ?

回答1:

The readObjectNoData section in Java Object Serialization Specification seems interesting (see below).

Your edits to the question give a perfect example. If Employee was serialized when it did not extend Person and later deserialized when it did then the Person part would be initialized to empty string and 0 age. Using this method, you can initialize them to "name" and 1 respectively.

For serializable objects, the readObjectNoData method allows a class to control the initialization of its own fields in the event that a subclass instance is deserialized and the serialization stream does not list the class in question as a superclass of the deserialized object. This may occur in cases where the receiving party uses a different version of the deserialized instance's class than the sending party, and the receiver's version extends classes that are not extended by the sender's version. This may also occur if the serialization stream has been tampered; hence, readObjectNoData is useful for initializing deserialized objects properly despite a "hostile" or incomplete source stream.

private void readObjectNoData() throws ObjectStreamException;

Each serializable class may define its own readObjectNoData method. If a serializable class does not define a readObjectNoData method, then in the circumstances listed above the fields of the class will be initialized to their default values (as listed in section 4.5.5 of The JavaTM Language Specification, Second Edition); this behavior is consistent with that of ObjectInputStream prior to version 1.4 of the JavaTM 2 SDK, Standard Edition, when support for readObjectNoData methods was introduced. If a serializable class does define a readObjectNoData method and the aforementioned conditions arise, then readObjectNoData will be invoked at the point during deserialization when a class-defined readObject method would otherwise be called had the class in question been listed by the stream as a superclass of the instance being deserialized.



回答2:

Are there any invariants in the Person class I created? When will they be violated?

None explicitly, but imagine that other methods in the class assume that name is never null and would throw NullPointerException if it ever were. In this case, the non-nullity of name is an invariant.

I copied the code for readObjectData() method in the Employee class , but it never got called. When will the method readObject() be called ?

There's no method readObjectData() involved with serialization, this must be a typo. The readObject() method is called every time a serialized object is deserialized.

The readObjectNoData() method is hit for some obscure corner case when deserializing a subclass of the class that contains the method.

The advanced serialization article on the SunOracle website covers the purpose of these serialization helper methods. I suggest you start there and post any subsequent questions you may run into.

(update)

In case you’re curious, the readObjectNoData method was added in release 1.4 to cover a corner case involving the addition of a serializable superclass to an existing serializable class. Details can be found in the serialization specification Serialization, 3.5.

The referenced text is:

For serializable objects, the readObjectNoData method allows a class to control the initialization of its own fields in the event that a subclass instance is deserialized and the serialization stream does not list the class in question as a superclass of the deserialized object. This may occur in cases where the receiving party uses a different version of the deserialized instance's class than the sending party, and the receiver's version extends classes that are not extended by the sender's version. This may also occur if the serialization stream has been tampered; hence, readObjectNoData is useful for initializing deserialized objects properly despite a "hostile" or incomplete source stream.

So this can happen in two cases:

  • The JVM that decodes the object stream has a newer version of the subclass being deserialized (Employee), one that extends some parent class (Person). The JVM that originally *en*coded the object stream has a different, older version of these classes, where Person was not yet a superclass of Employee.
  • Someone intentionally messed with the object stream in order to break things.


回答3:

"Extendable" means "can have a subclass".

readObjectNoData is used in an unusual case where the serializer (writer) is working with a version of a class with no base class, whereas the deserializer (reader) of the class has a version of the class that IS based on a subclass. The subclass can say "it's ok if my base class isn't in the serialized data - just make an empty one" by implementing readObjectNoData. See these release notes.



回答4:

An invariant in your Person class could be something like:

age must be greater than 0

So, when the Person class is instantiated with the default value, 0 (see here: http://download.oracle.com/javase/tutorial/java/nutsandbolts/datatypes.html) you will have violated that invariant.

However, given the default constructor you will be fine :)