Serialization: not trivial

(from "developing JavaBeans")
First, a preview of the use of introspection.

The example below is to show a technique for serialization on an object that contains a hash table that is not serializable.

The most important aspect of this example is that it is possible to add additional data to a stream that can be used during deserialization to reconstruct objects that are not serializable.

Consider a Generic Button Adapter with an event handler for a panel with three buttons on it. It is very cool - it stores buttons with their respective methods (actions to be performed). It uses the introspection capabilities of the Reflection package.

We cannot trivially make the adapter serializable by adding implements Serializable since it contains a hash table that holds Method objects and the Method class can't be serialized.

We could mark the instance of GenericButtonAdapter within the ListeningPanel as transient. Doing so would exclude the adapter from the process of serializing an instance of ListeningPanel. But the ListeningPanel class is not the only one that had a reference to the GenericButtonAdapter instance. For example, the adapter is the registered listener of action events for the buttons.

In a nutshell, don't try to take the easy way out by making all references to something hard to serialize transient. Instead, make the object Serializable! Go for it!

  1. modify the GenericButtonAdapter class to implements java.io.Serializable
  2. deal with the hash table: mark the java.util.Hashtable data member, which is called mappingTable as transient
  3. deal with how to reconstruct the hash table

The original implementation of the registeredActionEventHandler() method added the mappings to the hash table and registered the adapter as an action event listener for the button. Since we don't serialize the hash table, we need to rebuild the mappings as part of the deserialization process.

We need a method to add elements to the mappings-but we don't want to call the addActionListener() method to the button again, as this relationship already exists in the object stream being deserialized.

Let's introduce a new method called addMapping(), which adds elements to mappingTable.

Now, the registeredActionEventHandler()method invokes addMapping() and addActionEventListener() on the button. The addMapping() method allows us to receive the method mappings during deserialization. But we still need a mechanism to create an empty instance of the hash table. This can be handled by the addMapping() method. When it gets called, we can check to see if the hash table has been created. If it hasn't, it can be created there.

We need to implement writeObject() and readObject() so that we can restore the data that is held in the hash table.

The writeObject() method invokes defaultWriteObject() on the stream to store the non-transient data members, but now we need to store enough information from mappingTable so that it can be reconstructed. First consider if mappingTable has not been constructed yet. Otherwise we make a clone of the hash table within a synchronized block to ensure that no mappings are added while we are serializing.

The first step is to save the number of elements in mappingTable. Call (size()), then iterate over the keys and values of the hash table. We retrieve the Button and Method for each entry.

Doing writeObject() for the Button instances is no problem; but since Method is not serializable, we need a little work. All we really need is the method name, because we can use our addMapping() method to reconstruct the method object later. We get the method name by invoking its getName() method, which returns an instance of a String. This is easy to save with writeObject()

The readObject() is pretty straight-forward given the explanation of the writeObject().

Here is the code for the new Serializable Generic Button Adapter and here is a ListeningPanel that is paranoid and uses a writeObject() and readObject() to save a "sanity check" string to check that all is well. Finally, here is an application on which you can run

to save an instance of the frame to example8.tmp or to get the frame back.

Make thread Fields transient

Any thread fields declared inside a class whose instances are to be serialized must be marked as transient. This is because at the time a serialized bean is reinstaniated, the original context under which the program was running no longer exists.

to

You can't save the context of a thread and reload it in the same state because the thread's state depends on the runtime environment.

So, now add the writeObject() and readObject() serialization methods. When writing the object you can determine whether the bean's thread is running or not. When reading the serialized representation of the bean back into memory you must initialize the thread again.

If killme was true when the bean was saved, the bean's start method is called to reactivate the bean when it is reinstantiated. Had you saved out the appplication with the thread suspended, the thread would not run when you reinstantiated the bean.

Serializing Event Listeners

If you keep a Vector of event listeners, an attempt will be made to serialize the listeners when the Vector is serialized. Yet all of the Listener elements in the Vector might not be serializable.

One possible "solution" is to mark the collection of listeners transient - but this ignores some elements of the Vector that are serializable.

This text writes, "The best we can do is to serialize those listeners that can be serialized, and skip the others."

Their example is the SimpleTemperature class that fires a TemperatureChangedEvent. Notice the Vector is marked transient, and the writeObject() method saves what it can. Notice the use of instanceOf operator to see which objects are serializable.

A nice thing to know: if you are using one of the support classes, such as java.beans.PropertyChangeSupport you can allow the object to be serialized automatically. They use the above technique as well.