Java supports two techniques for object permanence: Serializable and Externalizable. These two are related but different. Serializable is more automatic and less flexible. Externalizable is less automatic but more flexible. Serializable is rumored to be slower in some implementations than Externalizable. Because Externalizable gives me a lot more flexibility, I choose to use it exclusively.
Making a class Externalizable is relatively easy. It amounts to implementing two methods: One for writing the state of your object and one for reading it. It also requires that you provide a no-parameter constructor so the system can automatically instantiate your object prior to asking you to read its fields from the file.
By default, any change you make to your class (such as adding a field) will cause any previously serialized data to be incompatible with the class. To resolve this, I take over responsibility for all versioning, as detailed below.
1. Implement Externalizable
Your class needs to implement Externalizable, which requires two functions:
public void writeExternal(ObjectOutput output) throws IOException
// Write the version number to output using writeInt().
// Write the fields from your object to output.
public void readExternal(
throws IOException, ClassNotFoundException
// Read and test the version number.
// Read the fields of this object in the same order that you wrote them.
2. Add a public constructor with no parameters
You need to add a no-parameter constructor to your class if there isn’t one already. It must be declared “public”. When reading your serialized objects, your object will be constructed using this constructor, then readExternal() will be called.
3. Define serialVersionUID
Add the following field to your class. This identifies the class so the serialization code knows what kind of object to instantiate in its readObject() method:
private static final long serialVersionUID = 91089626578087866L;
The value of this UID is irrelevant, but it should be unique among the other serialized classes in your document. I use random.org to generate a couple random numbers less than or equal to a million, then concatenate them to create a random long. If you omit this step, the compiler will generate a UID for you, but since it’s based on the class signature it will change if you change just about anything in the class. This will cause an exception to be thrown when you try to deserialize a previously created file. So it’s best to define your own.
4. Manage changes to your class fields
Since you’re defining your own serialVersionUID, you need to completely manage version changes in your serialized file. I create another constant for this purpose:
private static final int serializationVersion = 1;
In writeExternal() this is the first value I write to the file. When reading, I read this value and test it to make sure I understand the version of the file being read, then make decisions based on the version of the file I’m reading. I may have to supply default values for new fields added to the class since the file was written.
If I don’t recognize the version, I throw a new IOException with a description of the problem:
throw new IOException("Unrecognized archive version");
5. Notes on writeExternal()
In writeExternal(), use writeInt(), writeBoolean(), etc. to write field values. For enums, use writeObject(). For Strings, use writeUTF(). For your own classes, use writeObject(), then implement Externalizable in those classes.