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 ?
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.
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.
"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.
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 :)