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!
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
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.
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.
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.
to save an instance of the frame to example8.tmp
or
to get the frame back.
Make thread Fields transient
to
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.