By having the constructor not assign any values to fields*, and add an
init() method that does the initialization.
* This of course includes assignments in declarations:
int foo = 5;
is the same as
int foo;
public void <init>() {
foo = 5;
}
readObject has to work with arbitrary objects. It can't go composing
constructors or methods for them. It can't even count on a no-arg
constructor existing.
I spent a while looking at the code in java.util.ObjectInputStream. It
is pretty hairy.
readOrdinaryObject(boolean unshared)
uses desc.newInstance() to create an empty shell of an object later
filled in with the read.
That puzzled me on two counts:
1. what if there is no no-arg constructor?
2. I distinctly read somewhere that readObject does NOT invoke the
constructor.
The explanation is in the javaDoc for ObjectStreamClass.newInstance:
Creates a new instance of the represented class. If the class is
externalizable, invokes its public no-arg constructor; otherwise, if
the class is serializable, invokes the no-arg constructor of the first
non-serializable superclass. Throws UnsupportedOperationException if
his class descriptor is not associated with a class, if the associated
class is non-serializable or if the appropriate no-arg constructor is
inaccessible/unavailable.
On read, the code in the outer layer constructors of the serialized
objects does not get called, just the inner non-serializable layers,
e.g. Object.