[ Team LiB ] Previous Section Next Section

10.2 Custom Serialization

Not every piece of program state can, or should be, serialized. Such things as FileDescriptor objects are inherently platform-specific or virtual machine-dependent. If a FileDescriptor were serialized, for example, it would have no meaning when deserialized in a different virtual machine. For this reason, and also for the important security reasons described earlier, not all objects can be serialized.

Even when an object is serializable, it may not make sense for it to serialize all its state. Some fields may be "scratch" fields that can hold temporary or precomputed values but don't actually hold state needed to restore a serialized object. Consider a GUI component. It may define fields that store the coordinates of the last mouse click it received. This information is never of interest when the component is deserialized, so there's no need to bother saving the values of these fields as part of the component's state. To tell the serialization mechanism that a field shouldn't be saved, simply declare it transient:

protected transient short last_x, last_y; // Temporary fields for mouse pos

There are also situations where a field is not transient (i.e., it does contain an important part of an object's state), but for some reason it can't be successfully serialized. Consider another GUI component that computes its preferred size based on the size of the text it displays. Because fonts have slight size variations from platform to platform, this precomputed preferred size isn't valid if the component is serialized on one type of platform and deserialized on another. Since the preferred size fields will not be reliable when deserialized, they should be declared transient so that they don't take up space in the serialized object. But in this case, their values must be recomputed when the object is deserialized.

A class can define custom serialization and deserialization behavior (such as recomputing a preferred size) for its objects by implementing writeObject( ) and readObject( ) methods. Surprisingly, these methods are not defined by any interface, and they must be declared private. If a class defines these methods, the appropriate one is invoked by the ObjectOutputStream or ObjectInputStream when an object is serialized or deserialized.

For example, a GUI component might define a readObject( ) method to give it an opportunity to recompute its preferred size upon deserialization. The method might look like this:

private void readObject(ObjectInputStream in)
        throws IOException, ClassNotFoundException
  in.defaultReadObject( );      // Deserialize the component in the usual way.
  this.computePreferredSize( ); // But then go recompute its size.

This method calls the defaultReadObject( ) method of the ObjectInputStream to deserialize the object as normal and then takes care of the postprocessing it needs to perform.

Example 10-2 is a more complete example of custom serialization. It shows a class that implements a growable array of integers, like the IntList class of Example 2-7. This version of the class is declared Serializable and defines a writeObject( ) method to do some preprocessing before being serialized and a readObject( ) method to do postprocessing after deserialization. Note that the size field is declared transient. The example also overrides equals( ) so that serialization can be tested, and overrides hashCode( ) to match.

Example 10-2. SerialIntList.java
package com.davidflanagan.examples.serialization;
import java.io.*;

 * A simple class that implements a growable array of ints, and knows
 * how to serialize itself as efficiently as a nongrowable array.
public class SerialIntList implements Serializable {
    // These are the fields of this class.  By default the serialization
    // mechanism would just write them out.  But we've declared size to be
    // transient, which means it will not be serialized.  And we've
    // provided writeObject( ) and readObject( ) methods below to customize
    // the serialization process.
    protected int[  ] data = new int[8]; // An array to store the numbers.
    protected transient int size = 0;  // Index of next unused element of array
    /** Return an element of the array */
    public int get(int index) {
        if (index >= size) throw new ArrayIndexOutOfBoundsException(index);
        else return data[index];
    /** Add an int to the array, growing the array if necessary */
    public void add(int x) {
        if (data.length==size) resize(data.length*2);  // Grow array if needed.
        data[size++] = x;                              // Store the int in it.
    /** An internal method to change the allocated size of the array */
    protected void resize(int newsize) {
        int[  ] newdata = new int[newsize];            // Create a new array
        System.arraycopy(data, 0, newdata, 0, size); // Copy array elements.
        data = newdata;                              // Replace old array

     * Get rid of unused array elements before serializing the array.  This
     * may reduce the number of array elements to serialize.  It also makes
     * data.length == size, so there is no need to safe the (transient) size
     * field. The serialization mechanism will automatically call this method
     * when serializing an object of this class.  Note that this must be 
     * declared private.
    private void writeObject(ObjectOutputStream out) throws IOException {
        if (data.length > size) resize(size);  // Compact the array.
        out.defaultWriteObject( );              // Then write it out normally.
     * Restore the transient size field after deserializing the array.
     * The serialization mechanism automatically calls this method.
    private void readObject(ObjectInputStream in)
        throws IOException, ClassNotFoundException
        in.defaultReadObject( );                // Read the array normally.
        size = data.length;                    // Restore the transient field.

     * Does this object contain the same values as the object o?
     * We override this Object method so we can test the class.
    public boolean equals(Object o) {
        if (!(o instanceof IntList)) return false;
        IntList that = (IntList) o;
        if (this.size != that.size) return false;
        for(int i = 0; i < this.size; i++)
            if (this.data[i] != that.data[i]) return false;
        return true;

    /** We must override this method when we override equals( ). */
    public int hashCode( ) {
        int code = 1; // non-zero to hash [0] and [  ] to distinct values
        for(int i = 0; i < size; i++)
            code = code*997 + data[i];  // ignore overflow
        return code;

    /** A main( ) method to prove that it works */
    public static void main(String[  ] args) throws Exception {
        IntList list = new IntList( );
        for(int i = 0; i < 100; i++) list.add((int)(Math.random( )*40000));
        IntList copy = (IntList)Serializer.deepclone(list);
        if (list.equals(copy)) System.out.println("equal copies");
        Serializer.store(list, new File("intlist.ser"));
    [ Team LiB ] Previous Section Next Section