Sunday, February 3, 2008

Serialization in Java - readResolve(), writeReplace() and more

Quite some time since my last post :) A friend at work mentioned that you cannot serialize classes with a non-serializable parent class that does not have an empty constructor. Whew...

That sounds nice and confusing. So lets try to analyze and see how to implement serialization and what effect does each approach have on your class and its children and grandchildren.

First off you have to realize there are two interfaces: Serializable, which ostensibly has no methods, but actually provides four and Externalizable, which provides two.

Secondly the relationship between Externalizable and Serializable has to be made clear.
  • Externalizable ALWAYS supersedes Serializable.
  • Serializable is more of a "marker" interface - If you declare a class Serializable and customize the serialization behavior, usually, the child class will still get serialized properly in a chained fashion: The child class fields then the customized base class behavior.
  • I say usually because readResolve() and writeReplace() change this default behavior and we shall see below how.
  • If you declare a class Externalizable, it and all its child classes are Externalizable. The moment an Externalizable class is encountered, the Serializable optimizations shut down. It doesn't matter if you declare a derived class Serializable or not, the last overriden readExternal() and writeExternal() methods are the ones that will get called.
With that out of the way, lets see how these two are used:

Base Class Base Class
Empty Constructor
Derived Class
Empty Constructor
Derived Class Methods Grandchild
Serializable N/R N/R N/R N/R N/R
N/A Child Visible N/R Serializable readObject/writeObject N/R
N/A N/A Public Externalizable readExternal/writeExternal Public empty constructor needed. On fieldset change, override readExternal / writeExternal to serialize additional fields.
N/A N/A N/A Serializable Create DTO. Implement writeReplace. In DTO implement readResolve Create new DTO.
Override writeReplace. In DTO implement readResolve

Where N/R means Not Required and N/A means Not Available

In a nutshell,
  • If you have a base class which is Serializable, you are home free... No need to do anything
  • If you have a base class which is not Serializable but does have a child visible (public/protected/package) empty constructor then you implement readObject() and writeObject() in your class and you are done.
  • If your non-Serializable base class does not have an empty constructor then readObject() and writeObject() will not work. You will get an Exception on attempting to Serialize
  • In this case, if your class has an empty constructor, it can implement Externalizable and then it will get Serialized.
  • However, any child classes will transparently get serialized without their new fields, so if you want new fields in child classes to get serialized they must override the readExternal() and writeExternal() methods
  • Lastly we come to the most interesting option: readResolve() and writeReplace(): This actually allows you to replace your class with a DTO at Serialization time. Note that both the methods are implemented in different classes. writeReplace() in the DO and readResolve() in the DTO.
  • You can of course implement only one method, this will mean, say if you only implement writeReplace() in a class then every time it gets written, it will be replaced on the stream by an instance of the DTO class, however, without the readResolve(), on reading you will still get a instance of the DTO not the DO
  • writeReplace() and readResolve() will also mean that your class is no longer serializable in the normal way: any child class which tried to get Serialized and does NOT override the writeReplace() method will get an Exception
Now for some example of how readResolve() and writeReplace() can be used:
Lets say we have a base class, Base

public class Base {
private int a;
private int b;

public Base(int a, int b) {
this.a = a;
this.b = b;
}

/*
* Add Getters and Setters...
*/
}


This has a derived class, Derived4

public class Derived4 extends Base implements Serializable {
private int c;

public Derived4(int a, int b, int c) {
super(a,b);
this.c = c;
}

public Derived4(DataToken d) {
super(d.getA(),d.getB());
c = d.getC();
}

/*
* Add Getters and Setters...
*/

private Object writeReplace() throws ObjectStreamException {
Derived4DataToken d = new Derived4DataToken();
d.setA(this.getA());
d.setB(this.getB());
d.setC(this.getC());
return d;
}
}


Note the writeReplace()? Now, lets see the DTO class: Derived4DataToken

public class Derived4DataToken implements Serializable {
private int a;
private int b;
private int c;

private static final long serialVersionUID = -2835599374077375317L;

/*
* Add Getters and Setters...
*/

private Object readResolve() throws ObjectStreamException {
return new Derived4(this);
}
}


So now, we can go ahead and read and write the class, exactly as normal. Note that if Derived4DataToken had not implemented readResolve(), reading the data stream would have resulted in a Derived4DataToken instance, not a Derived4 instance.

ByteArrayOutputStream os = new ByteArrayOutputStream();
Derived4 d1 = new Derived4(10, 20, 30);
ObjectOutputStream oos = new ObjectOutputStream(os);
oos.writeObject(d1);
oos.flush();
Derived4 d2;
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(os.toByteArray()));
d2 = (Derived4)ois.readObject();
out.println("Derived4=> a : b : c :: " + d2.getA() + " : " + d2.getB() + " : " + d2.getC());


Finally, let me add that even if you are not making your class Serializable, if you want to be safe such that child classes can be Serializable then the cleanest solution is to have a child visible empty constructor, it will save you much heartache later.

As I sign off, let me add that the HTML highlighting of Java code that has been done here has been done using the Java2Html converter applet here.
Post a Comment