[ Team LiB ] Previous Section Next Section

10.4 Serialization and Class Versioning

One of the features of Example 10-3 is that it includes a version number in the serialization stream it writes. This is useful if the class evolves in the future and needs to use a new serialization format. The version number allows future versions of the class to recognize serialized objects written by this version of the class.

For Serializable objects that are not Externalizable, the Serialization API handles versioning itself. When an object is serialized, some information about the object's class must obviously be serialized with it, so that the correct class file can be loaded when the object is deserialized. This information about the class is represented by the java.io.ObjectStreamClass class. It contains the fully qualified name of the class and a version number for the class. The version number is very important because an early version of a class may not be able to deserialize a serialized instance created by a later version of the same class. The version number for a class is a long value. By default, the serialization mechanism creates a unique version number by computing a hash of the name of the class, the name of its superclass and any interfaces it implements, the name and type of its fields, and the name and type of its nonprivate methods. Thus, whenever you add a new method, change the name of a field, or make even minor modifications to the API or implementation of a class, its computed version number changes. When an object is serialized by one version of a class, it can't be deserialized by a version that has a different version number.

Thus, when you make changes to a serializable class, even minor changes that don't affect the serialization format, you break serialization compatibility between versions. For example, our SerialIntList class really ought to have a set( ) method that sets the value of a specified element of the list. But if you add this method, the new version of the class can't deserialize objects serialized by the old version. The way to prevent this problem is to explicitly declare a version number for your class. You do this by giving the class a constant field named serialVersionUID.

The value of this field doesn't matter; it must simply be the same for all versions of the class that have a compatible serialization format. Since the SerialIntList class shown in Example 10-2 doesn't have a serialVersionUID field, its version number was implicitly computed based on the API of the class. In order to give an updated version of SerialIntList a version number that matches the original version, use the serialver command that comes with the Java SDK:

% serialver je3.serialization.SerialIntList
SerialIntList:    static final long serialVersionUID = 2952055272471088220L;

If you run this program on the original version of the class, it prints a field definition suitable for inclusion in the modified version of the class. By including this constant field in the modified class, you retain serialization compatibility between it and the original version.

10.4.1 Advanced Versioning

Sometimes you make changes to a class that alter the way the class stores its state. Imagine a Rectangle class that represents the rectangle as the coordinate of the upper-left corner, plus a width and a height. Now suppose that the class is reimplemented so that it maintains exactly the same public API, but the rectangle is now represented by two points: the coordinates of the upper-left corner and the lower-right corner. The internal private fields of the class have changed, so it would appear that serialization compatibility between the two implementations of the class is simply not possible.

One solution is to make the new version of the class Externalizable, taking full control over the data format, and using that to write the new implementation fields in the old format. In Java 1.2 and later, however, the serialization mechanism has been updated to allow the serialization format of Serializable classes to be totally decoupled from the fields used by a particular implementation or version of the class. A class can now declare a private field named serialPersistentFields that refers to an array of java.io.ObjectStreamField objects. Each of these objects defines a field name and a field type. These fields need not have any relationship to the fields implemented by the class; they are the fields of the serialized form of the class. By defining this array of ObjectStreamField objects, the class is specifying its serialization format. When a new version of the class is defined, that new version must be able to save and restore its state in the format defined by the serialPersistentFields array.

The techniques for reading and writing the serialization fields declared by the serialPersistentFields array are beyond the scope of this chapter. For more information, check the putFields( ) and writeFields( ) methods of ObjectOutputStream and the readFields( ) method of ObjectInputStream. See also the advanced serialization examples supplied as part of the Java SDK documentation.

    [ Team LiB ] Previous Section Next Section