Serialization is the basic persistence support that is built into the Java language. It is called serialization because the objects are written or read sequentially as a series of bytes. An object is serializable if its class implements the serializable interface and all its non-transient members are as well serializable. When an object is serialized, the default algorithm traverses the closure of the object graph and writes all reachable objects to a stream. If a member of a class is tagged transient , the default algorithm skips this field. When the stream is read back, the equivalent object graph is rebuilt, keeping transient members initialized to their null values. By tagging a field as transient , its referenced object or content is not serialized by the default algorithm. On the contrary, fields that are not tagged transient are not checked by the Java compiler. For example, an ArrayList itself is serializable because it implements the Serializable interface, but because an instance of ArrayList might refer to any instance of java.lang.Object , the graph might still not be serializable at runtime. 2.2.1 The serialization APIThe Serializable interface has no declared methods. It is merely a tagging interface, although two methods are frequently used in conjunction with Serializable : readObject and writeObject . The following code copies a SerializableBook into a byte array and back into another SerializableBook : import java.io.*; import java.util.*; class SerializableBook implements Serializable { String name; SerializableBook(String n) { name = n; } } public class SerialTest { public static void main(String args[]) throws Exception { SerializableBook object; object = new SerializableBook("foo"); // create a buffer to write the object into: ByteArrayOutputStream bo; bo = new ByteArrayOutputStream(); // create a stream for serial object data: ObjectOutputStream oo = new ObjectOutputStream(bo); // write the object (and all its members recursively) oo.writeObject(object); // don't forget to close the stream: oo.close(); // now lets get the object back from the stream: // get a new stream that reads the buffer of the // previous stream: ByteArrayInputStream bi = new ByteArrayInputStream(bo.toByteArray()); // get a stream that can read objects: ObjectInputStream oi = new ObjectInputStream(bi); // cast the read object to our type: SerializableBook back; back = SerializableBook)oi.readObject(); // never ever forget to close a stream: oi.close(); } Three important things can be seen here:
In the example code, ByteArrayInputStream and ByteArrayOutputStream , respectively, may be replaced by any other Java stream implementation. This is what makes serialization a powerful component of the Java platform: Simply anything may be streamed through a pipe, a network connection, a file, or into a buffer. Other components make heavy use of serialization, such as Remote Method Invocation (RMI), for example. Needless to say, serialization works across platforms and is independent of the CPU byte order or other operating system dependencies. 2.2.2 Versioning and serializationOne of the obstacles with serialization is version handling. When anything changes in the class layout ”the class name, package name, inheritance, fields, or access modifiers ”the new version of the class must be recognized by the serialization runtime library. This is done by a so-called serial version unique identifier (serial version UID), which is a kind of hash code over all properties of a class. The default algorithm throws a StreamCorruptedException or an InvalidClassException if the serialized object data is incompatible with the class in memory. That is one reason why developers need to overload the readObject and writeObject methods with their own implementations to handle different versions of a class. Here is an example of how to handle the versioning problem: class Author implements Serializable { String name; Author(String n) { name = n; } private void writeObject(ObjectOutputStream out) throws IOException { int version = 1; out.writeInt(version); out.writeUTF(name); } private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { int version = in.readInt(); if (version == 1) { name = in.readUTF(); } else { throw new IOException( "Invalid version: "+version); } } } Now that a version is provided with every object in the stream, the readObject method can easily distinguish old and new objects. The second version of the Author class gets its name attribute split into first name and last name: class Author implements Serializable { String firstName; String lastName; Author(String first, String last) { firstName = first; lastName = last; } private void writeObject(ObjectOutputStream out) throws IOException { int version = 2; out.writeInt(version); out.writeUTF(firstName); out.writeUTF(lastName); } private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { int version = in.readInt(); if (version == 2) { firstName = in.readUTF(); lastName = in.readUTF(); } else if (version == 1) { // fallback: String name = in.readUTF8(); firstName = guessFirstName(name); lastName = guessLastName(name); } else { throw new IOException( "Invalid version: "+version); } } } 2.2.3 When to use object serializationAn application should use serialization when simple data types have to be saved persistently. Configuration data, which is entered through dialog boxes or window positions in GUI applications, is a good candidate for serialization. When the application version changes, such settings may be simply ignored and complex version handling may not be required. Another use case is RMI: an RMI method may take any object as a parameter if the object and its referenced attributes are serializable. The same applies to method return values. Complex version handling is required only for applications that must support incompatible client and server application versions. 2.2.4 When not to use object serializationSerialization does, however, have serious limitations that exclude it as a candidate technology for storing even simple domain objects (e.g.. clients , bills, issues, and so on) of real-world applications. Most notably, Java JDK serialization lacks the following:
The next section examines how relational databases can be used with an object persistence framework as a data storage technology. |