DataInputStream / DataOutputStream enables you to perform I/O for primitive type values and strings. ObjectInputStream / ObjectOutputStream enables you to perform I/O for objects in addition to primitive type values and strings. Since ObjectInput - Stream / ObjectOutputStream contains all the functions of DataInputStream / DataOutputStream , you can replace DataInputStream / DataOutputStream completely with ObjectInputStream / ObjectOutputStream .
ObjectInputStream extends InputStream and implements ObjectInput and ObjectStreamConstants , as shown in Figure 18.14. ObjectInput is a subinterface of DataInput . DataInput is shown in Figure 18.9. ObjectStreamConstants contains the constants to support ObjectInputStream / ObjectOutputStream .
ObjectOutputStream extends OutputStream and implements ObjectOutput and ObjectStreamConstants , as shown in Figure 18.15. ObjectOutput is a subinterface of DataOutput . DataOutput is shown in Figure 18.10.
You may wrap an ObjectInputStream / ObjectOutputStream on any InputStream / OutputStream using the following constructors:
// Create an ObjectInputStream public ObjectInputStream(InputStream in) // Create an ObjectOutputStream public ObjectOutputStream(OutputStream out)
Listing 18.4 writes student names , scores, and current date to a file named object.dat .
1 import java.io.*; 2 3 public class TestObjectOutputStream { 4 public static void main(String[] args) throws IOException { 5 // Create an output stream for file object.dat 6 ObjectOutputStream output = 7 new ObjectOutputStream( new FileOutputStream( "object.dat" )); 8 9 // Write a string, double value, and object to the file 10 output.writeUTF( "John" ); 11 output.writeDouble( 85.5 ); 12 output.writeObject( new java.util.Date()); 13 14 // Close output stream 15 output.close(); 16 } 17 } |
An ObjectOutputStream is created to write data into file object.dat in lines 6 “7. A string, a double value, and an object are written to the file in lines 10 “12. To improve performance, you may add a buffer in the stream using the following statement to replace lines 6 “7:
ObjectOutputStream output = new ObjectOutputStream( new BufferedOutputStream( new FileOutputStream( "object.dat" )));
Multiple objects or primitives can be written to the stream. The objects must be read back from the corresponding ObjectInputStream with the same types and in the same order as they were written. Java's safe casting should be used to get the desired type. Listing 18.5 reads data back from object.dat .
1 import java.io.*; 2 3 public class TestObjectInputStream { 4 public static void main(String[] args) 5 throws ClassNotFoundException, IOException { 6 // Create an input stream for file object.dat 7 ObjectInputStream input = 8 new ObjectInputStream( new FileInputStream( "object.dat" )); 9 10 // Write a string, double value, and object to the file 11 String name = input.readUTF(); 12 double score = input.readDouble(); 13 java.util.Date date = (java.util.Date)( input.readObject() ); 14 System.out.println(name + " " + score + " " + date); 15 16 // Close output stream 17 input.close(); 18 } 19 } |
The readObject() method may throw java.lang.ClassNotFoundException . The reason is that when the JVM restores an object, it first loads the class for the object if the class has not been loaded. Since ClassNotFoundException is a checked exception, the main method declares to throw it in line 5. An ObjectInputStream is created to read input from object.dat in lines 7 “8. You have to read the data from the file in the same order and format as they were written to the file. A string, a double value, and an object are read in lines 11 “13. Since readObject() returns an Object , it is cast into Date and assigned to a Date variable in line 13.
Not every object can be written to an output stream. Objects that can be written to an object stream are said to be serializable . A serializable object is an instance of the java.io.Serializable interface, so the class of a serializable object must implement Serializable .
The Serializable interface is a marker interface. Since it has no methods , you don't need to add additional code in your class that implements Serializable . Implementing this interface enables the Java serialization mechanism to automate the process of storing objects and arrays.
To appreciate this automation feature and understand how an object is stored, consider what you need to do in order to store an object without using this feature. Suppose you want to store a JButton object. To do this you need to store all the current values of the properties (e.g., color , font, text, alignment) in the object. Since JButton is a subclass of AbstractButton , the property values of AbstractButton have to be stored as well as the properties of all the superclasses of AbstractButton . If a property is of an object type (e.g., background of the Color type), storing it requires storing all the property values inside this object. As you can see, this is a very tedious process. Fortunately, you don't have to go through it manually. Java provides a built-in mechanism to automate the process of writing objects. This process is referred to as object serialization , which is implemented in ObjectOutputStream . In contrast, the process of reading objects is referred to as object deserialization , which is implemented in ObjectInputStream .
Many classes in the Java API implement Serializable . The utility classes, such as java.util.Date , and all the Swing GUI component classes implement Serializable . Attempting to store an object that does not support the Serializable interface would cause a NotSerializableException .
When a serializable object is stored, the class of the object is encoded; this includes the class name and the signature of the class, the values of the object's instance variables, and the closure of any other objects referenced from the initial object. The values of the object's static variables are not stored.
Note
If an object is an instance of Serializable but contains nonserializable instance data fields, can it be serialized? The answer is no. To enable the object to be serialized, mark these data fields with the transient keyword to tell the JVM to ignore them when writing the object to an object stream. Consider the following class: public class Foo implements java.io.Serializable { private int v1; private static double v2; private transient A v3 = new A(); } class A { } // A is not serializable When an object of the Foo class is serialized, only variable v1 is serialized. Variable v2 is not serialized because it is a static variable, and variable v3 is not serialized because it is marked transient . If v3 were not marked transient , a java.io.NotSerializable Exception would occur. |
Note
If an object is written to an object stream more than once, will it be stored in multiple copies? The answer is no. When an object is written for the first time, a serial number is created for it. The JVM writes the complete content of the object along with the serial number into the object stream. After the first time, only the serial number is stored if the same object is written again. When the objects are read back, their references are the same, since only one object is actually created in the memory. |
An array is serializable if all its elements are serializable. An entire array can be saved using writeObject into a file and later can be restored using readObject . Listing 18.6 stores an array of five int values, an array of three strings, and an array of two JButton objects, and reads them back to display on the console.
1 import java.io.*; 2 import javax.swing.*; 3 4 public class TestObjectStreamForArray { 5 public static void main(String[] args) 6 throws ClassNotFoundException, IOException { 7 int [] numbers = { 1 , 2 , 3 , 4 , 5 }; 8 String[] strings = { "John" , "Jim" , "Jake" }; 9 JButton[] buttons = { new JButton( "OK" ), new JButton( "Cancel" )}; 10 11 // Create an output stream for file array.dat 12 ObjectOutputStream output = 13 new ObjectOutputStream ( new FileOutputStream( "array.dat" , true )); 14 15 // Write arrays to the object output stream 16 output.writeObject(numbers); 17 output.writeObject(strings); 18 output.writeObject(buttons); 19 20 // Close the stream 21 output.close(); 22 23 // Create an input stream for file array.dat 24 ObjectInputStream input = 25 new ObjectInputStream( new FileInputStream( "array.dat" )); 26 27 int [] newNumbers = ( int [])(input.readObject()); 28 String[] newStrings = (String[])(input.readObject()); 29 JButton[] newButtons = (JButton[])(input.readObject()); 30 31 // Display arrays 32 for ( int i = ; i < newNumbers.length; i++) 33 System.out.print(newNumbers[i] + " " ); 34 System.out.println(); 35 36 for ( int i = ; i < newStrings.length; i++) 37 System.out.print(newStrings[i] + " " ); 38 System.out.println(); 39 40 for ( int i = ; i < newButtons.length; i++) 41 System.out.print(newButtons[i].getText() + " " ); 42 } 43 } |
Lines 16 “18 write three arrays into file array.dat . Lines 27 “29 read three arrays back in the same order they were written. Since readObject() returns Object , casting is used to cast the objects into int[] , String[] , and JButton[] .