Java Examples in a Nutshell, 3rd Edition
Despite the power and importance of serialization, it is performed using a simple API that forms part of the java.io package: an object is serialized by the writeObject( ) method of the ObjectOutputStream class and deserialized by the readObject( ) method of the ObjectInputStream class. These classes are byte streams like the various other streams we saw in Chapter 3. They implement the ObjectOutput and ObjectInput interfaces, respectively, and these interfaces extend the DataOutput and DataInput interfaces. This means that ObjectOutputStream defines the same methods as DataOutputStream for writing primitive values, while ObjectInputStream defines the same methods as DataInputStream for reading primitive values. The methods we're interested in here, however, are writeObject( ) and readObject( ), which write and read objects. Only objects that implement the java.io.Serializable interface may be serialized. Serializable is a marker interface; it doesn't define any methods that need to be implemented. Nevertheless, for security reasons, some classes don't want their private state to be exposed by the serialization mechanism. Therefore, a class must explicitly declare itself to be serializable by implementing this interface. An object is serialized by passing it to the writeObject( ) method of an ObjectOutputStream. This writes out the values of all of its fields, including private fields and fields inherited from superclasses. The values of primitive fields are simply written to the stream as they would be with a DataOutputStream. When a field in an object refers to another object, an array, or a string, however, the writeObject( ) method is invoked recursively to serialize that object as well. If that object (or an array element) refers to another object, writeObject( ) is again invoked recursively. Thus, a single call to writeObject( ) may result in an entire object graph being serialized. When two or more objects each refer to the other, the serialization algorithm is careful to output each object only once; writeObject( ) can't enter infinite recursion. Deserializing an object simply follows the reverse of this process. An object is read from a stream of data by calling the readObject( ) method of an ObjectInputStream. This creates a copy of the object in the state it was in when serialized. If the object refers to other objects, they are recursively deserialized as well. Example 10-1 demonstrates the basics of serialization. The example defines generic methods that can store and retrieve any serializable object's state to and from a file. It also includes an interesting deepclone( ) method that uses serialization to copy an object graph. The example includes a Serializable inner class and a test class that demonstrates the methods at work. Example 10-1. Serializer.java
package je3.serialization; import java.io.*; /** * This class defines utility routines that use Java serialization. **/ public class Serializer { /** * Serialize the object o (and any Serializable objects it refers to) and * store its serialized state in File f. **/ static void store(Serializable o, File f) throws IOException { ObjectOutputStream out = // The class for serialization new ObjectOutputStream(new FileOutputStream(f)); out.writeObject(o); // This method serializes an object graph out.close( ); } /** * Deserialize the contents of File f and return the resulting object **/ static Object load(File f) throws IOException, ClassNotFoundException { ObjectInputStream in = // The class for deserialization new ObjectInputStream(new FileInputStream(f)); return in.readObject( ); // This method deserializes an object graph } /** * Use object serialization to make a "deep clone" of the object o. * This method serializes o and all objects it refers to, and then * deserializes that graph of objects, which means that everything is * copied. This differs from the clone( ) method of an object, which is * usually implemented to produce a "shallow" clone that copies references * to other objects, instead of copying all referenced objects. **/ static Object deepclone(final Serializable o) throws IOException, ClassNotFoundException { // Create a connected pair of "piped" streams. // We'll write bytes to one, and then from the other one. final PipedOutputStream pipeout = new PipedOutputStream( ); PipedInputStream pipein = new PipedInputStream(pipeout); // Now define an independent thread to serialize the object and write // its bytes to the PipedOutputStream Thread writer = new Thread( ) { public void run( ) { ObjectOutputStream out = null; try { out = new ObjectOutputStream(pipeout); out.writeObject(o); } catch(IOException e) { } finally { try { out.close( ); } catch (Exception e) { } } } }; writer.start( ); // Make the thread start serializing and writing // Meanwhile, in this thread, read and deserialize from the piped // input stream. The resulting object is a deep clone of the original. ObjectInputStream in = new ObjectInputStream(pipein); return in.readObject( ); } /** * This is a simple serializable data structure that we use below for * testing the methods above **/ public static class DataStructure implements Serializable { String message; int[ ] data; DataStructure other; public String toString( ) { String s = message; for(int i = 0; i < data.length; i++) s += " " + data[i]; if (other != null) s += "\n\t" + other.toString( ); return s; } } /** This class defines a main( ) method for testing */ public static class Test { public static void main(String[ ] args) throws IOException, ClassNotFoundException { // Create a simple object graph DataStructure ds = new DataStructure( ); ds.message = "hello world"; ds.data = new int[ ] { 1, 2, 3, 4 }; ds.other = new DataStructure( ); ds.other.message = "nested structure"; ds.other.data = new int[ ] { 9, 8, 7 }; // Display the original object graph System.out.println("Original data structure: " + ds); // Output it to a file File f = new File("datastructure.ser"); System.out.println("Storing to a file..."); Serializer.store(ds, f); // Read it back from the file, and display it again ds = (DataStructure) Serializer.load(f); System.out.println("Read from the file: " + ds); // Create a deep clone and display that. After making the copy, // modify the original to prove that the clone is "deep". DataStructure ds2 = (DataStructure) Serializer.deepclone(ds); ds.other.message = null; ds.other.data = null; // Change original System.out.println("Deep clone: " + ds2); } } } |