Using a
DataOutputStream
, you could write an application
that saves the data content of an arbitrary object as simple types.
However Java provides an even more powerful mechanism called
object serialization that does almost all of the
work for you. In its simplest form, object serialization is an
automatic way to save and load the state of an object. However,
object serialization has depths that we cannot plumb within the scope
of this book, including complete control over the serialization
process and interesting conundrums like class versioning.
Basically, an object of any class
that implements the Serializable
interface can be
saved and restored from a stream. Special stream
subclasses,
ObjectInputStream
and
ObjectOutputStream
, are used to serialize primitive types
and objects. Subclasses of Serializable
classes
are also serializable. The default serialization mechanism saves the
value of an object’s nonstatic and nontransient (see the
following explanation) member variables.
One of the most important (and tricky) things about serialization is
that when an object is serialized, any object references it contains
are also serialized. Serialization can capture entire
“graphs” of interconnected objects and put them back
together on the receiving end (we’ll demonstrate this in an
upcoming example). The implication is that any object we serialize
must contain only references to other Serializable
objects. We can take control by marking nonserializable members as
transient
or overriding the default serialization
mechanisms. The transient
modifier can be applied to any instance variable to indicate that its
contents are not useful outside of the current context and should
never be saved.
In the following example, we create a
Hashtable
and write it to a disk file called
h.ser. The Hashtable
object
is serializable because it implements the
Serializable
interface.
//file: Save.java import java.io.*; import java.util.*; public class Save { public static void main(String[] args) { Hashtable h = new Hashtable( ); h.put("string", "Gabriel Garcia Marquez"); h.put("int", new Integer(26)); h.put("double", new Double(Math.PI)); try { FileOutputStream fileOut = new FileOutputStream("h.ser"); ObjectOutputStream out = new ObjectOutputStream(fileOut); out.writeObject(h); } catch (Exception e) { System.out.println(e); } } }
First we construct a Hashtable
with a few elements
in it. Then, in the three lines of code inside the
try
block, we write the
Hashtable
to a file called
h.ser, using
the
writeObject( )
method of
ObjectOutputStream
. The
ObjectOutputStream
class is a lot like the
DataOutputStream
class, except that it includes
the powerful writeObject( )
method.
The Hashtable
we created has internal references
to the items it contains. Thus, these components are automatically
serialized along with the Hashtable
. We’ll
see this in the next example when we deserialize the
Hashtable
.
//file: Load.java import java.io.*; import java.util.*; public class Load { public static void main(String[] args) { try { FileInputStream fileIn = new FileInputStream("h.ser"); ObjectInputStream in = new ObjectInputStream(fileIn); Hashtable h = (Hashtable)in.readObject( ); System.out.println(h.toString( )); } catch (Exception e) { System.out.println(e); } } }
In this example, we read the
Hashtable
from the h.ser
file, using the readObject( )
method of
ObjectInputStream
. The
ObjectInputStream
class is a lot like
DataInputStream
, except that it includes the
readObject( )
method. The return type of
readObject( )
is Object
, so we
need to cast it to a Hashtable
. Finally, we print
out the contents of the Hashtable
using its
toString( )
method.
We’ll show more examples of serialization at work in Chapter 19, when we discuss JavaBeans. There we’ll see that it is even possible to serialize graphical GUI components in mid-use and bring them back to life later.