Using streams and files, you can write an application that saves and loads its data to a disk drive. Java 1.1 provides an even more powerful mechanism called object serialization that does a lot 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, 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 member variables.
One of the 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 see this in an upcoming example. The implication is that any object we
serialize must only contain references to Serializable
objects. There are ways around this problem, like 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:
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 object is serializable because
it implements the
Serializable interface.
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:
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 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 see more examples of serialization at work in Chapter 18 when we discuss JavaBeans. There we'll see that it is even possible to serialize graphical AWT components in mid-use and bring them back to life later.