Richard G Baldwin (512) 223-4758, baldwin@austin.cc.tx.us, http://www2.austin.cc.tx.us/baldwin/

JDK 1.1, Object Serialization, A First Look

Java Programming, Lecture Notes # 250, Revised 12/17/98.


Introduction

Students in Prof. Baldwin's Advanced Java Programming classes at ACC are responsible for knowing and understanding all of the material in this lesson.

JDK 1.1 was formally released on February 18, 1997. This lesson was originally written on February 25, 1997 using JDK 1.1 software and documentation. It was upgraded to JDK 1.2 on 12/17/98

In a nutshell, the Object Serialization capability of JDK 1.1 makes it possible to write objects to streams and read objects from streams without the requirement to decompose those objects into their component parts before writing, and without the requirement to reconstruct the object from its component parts after reading.

More specifically, according to the documentation for JDK 1.1:

"Object Serialization extends the core Java Input/Output classes with support for objects. Object Serialization supports the encoding of objects, and the objects reachable from them, into a stream of bytes; and it supports the complementary reconstruction of the object graph from the stream. Serialization is used for lightweight persistence and for communication via sockets or Remote Method Invocation (RMI). The default encoding of objects protects private and transient data, and supports the evolution of the classes. A class may implement its own external encoding and is then solely responsible for the external format."

While some aspects of Object Serialization such as evolution of classes and external encoding can be very complex, in its basic form, object serialization is easy to use.

Overview

Object serialization provides the capability to store and retrieve Java objects. This capability is provided on the basis of streams so that objects can be "stored" and "retrieved" from any medium that supports streams in Java. This includes disk files, communication links via sockets, stream buffers in memory, etc.

Providing this capability requires the ability to represent the state of objects in a serialized form that can be used to automatically reconstruct the objects.

In order for an object to be saved in a stream, the class of which it is an instance must support either the Serializable or the Externalizable Interface. This lesson will deal only with the Serializable Interface. The Externalizable interface extends the Serializable interface, adding the methods writeExternal() and readExternal() that you can use to gain more control over the serialization process.

According to the JDK 1.1 documentation:

"For Java objects, the serialized form must be able to identify and verify the Java class from which the object's contents were saved and to restore the contents to a new instance. For Serializable objects, the stream includes sufficient information to restore the fields in the stream to a compatible version of the class."

Those objects which are to be stored in and retrieved from a stream often contain references to other objects (embedded objects). Those embedded objects must be stored and retrieved along with the parent object in order to maintain the relationship between objects. When an object is stored, all of the objects that are reachable from that object are stored as well.

A sample program will be presented later in this lesson which demonstrates the storage and retrieval of an object which contains another embedded object.

Writing an Object to a Stream

For the simple case of storing objects in a stream, we first instantiate an OutputStream to receive the bytes. In our later sample program, we will use a FileOutputStream.

Next, we need to instantiate an ObjectOutputStream object by invoking the constructor of the ObjectOutputStream class and passing the previously instantiated OutputStream object as a parameter.

Having done this, we can simply invoke the writeObject() method of the ObjectOutputStream class, passing the object that we want to store as a parameter.

In order to be able to pass the object to the writeObject() method, that object must belong to a class that implements the Serializable (or Externalizable) interface.

After storing all of our objects, we should use the flush() method to make certain that all of the objects have been inserted into the stream and are not still lingering in a buffer.

According to the JDK 1.1 documentation:

"The writeObject method serializes the specified object and traverses its references to other objects in the object graph recursively to create a complete serialized representation of the graph. Within a stream, the first reference to any object results in the object being serialized or externalized and the assignment of a handle for that object. Subsequent references to that object are encoded as the handle. Using object handles preserves sharing and circular references that occur naturally in object graphs. Subsequent references to an object use only the handle allowing a very compact representation."

Note that the ObjectOutputStream class also provides write methods for primitive types such as writeChar(), writeByte, writeDouble, etc., to support the possibility that primitive types may need to be written into the stream along with objects.

Reading an Object from the Stream

Retrieving a stored object, or reading an object from a stream is just as straightforward as writing it to the stream.

First, we need to instantiate an InputStream. In the sample program later in this lesson, we will use a FileInputStream.

Then we instantiate an ObjectInputStream that reads from the InputStream. This is accomplished by invoking the constructor for the ObjectInputStream class and passing the InputStream as a parameter.

Having done this, we simply use the readObject() method of the ObjectInputStream class to read each object from the stream. This method returns an object of type Object, so we need to downcast it to the proper type of object before storing or attempting to manipulate it.

According to the JDK 1.1 documentation:

"The readObject method deserializes the next object in the stream and traverses its references to other objects recursively to create the complete graph of objects serialized."

Note that the ObjectInputStream class also provides read methods for primitive types such as readChar(), readByte, readDouble, etc., to support the possibility that primitive types may have been written into the stream along with objects.

Protecting Sensitive Information

If your objects contain sensitive information, you should read the JDK 1.1 documentation very carefully before writing the objects into a stream.

According to the documentation, one technique for avoiding security problems is to mark sensitive fields as private transient. transient and static fields are not serialized or deserialized. This will prevent the sensitive data from actually entering the stream.

Also according to the documentation:

"Particularly sensitive classes should not be serialized at all. To accomplish this, the object should not implement either the Serializable or Externalizable interfaces."

Since object serialization is implemented as a stream process which inherently supports the application of filters, it is also possible to provide encryption and decryption filters.

Sample Program

The next section discusses the particulars of a sample program that illustrates much of what has been discussed above. A listing of the program follows the discussion.

Interesting Code Fragments

This application

The three objects written to the output file are:

The following code fragment shows the class definition for MyEmbeddedClass. This class contains one private instance variable and one public instance method. The private instance variable is a simple String object. The public instance method is a showEmbeddedData() method that displays the private instance variable.

Note that this class implements the Serializable interface.

class MyEmbeddedClass implements Serializable{
  private String myEmbeddedString = "I am a " +
        "Private Instance Variable in the Embedded Object";
  public void showEmbeddedData(){
    System.out.println(myEmbeddedString);
  }//end showEmbeddedData  
}//end class MyEmbeddedClass

The next fragment shows the class definition for the class named MyOuterClass. This class contains two private instance variables and a public instance method named ShowAllData().

The first instance variable is a simple String object. The second instance variable is an embedded object of the class discussed above (MyEmbeddedClass) which also contains a private instance variable and a public instance method.

The public instance method in an object of this class is a showAllData() method that displays the private String variable in the object and invokes the public showEmbeddedData() method of the private embedded object to cause the private instance variable in that object to also be displayed.

Note that the class implements the Serializable interface.

class MyOuterClass implements Serializable{
  private String myOuterString = "I am a " +
      "Private Instance Variable in the Outer Object";
  MyEmbeddedClass myEmbeddedObject = new MyEmbeddedClass();

  public void showAllData(){
    System.out.println(myOuterString);
    myEmbeddedObject.showEmbeddedData();
  }//end showAllData()  

}//end class MyOuterClass

The controlling class for the application is shown in the next fragment. It is very simple. All that it does is to instantiate an object of the MyProcess class where all of the work is done..

class ObjSer01{//controlling class
  static public void main(String[] args){
  MyProcess myProcess = new MyProcess();  
  }//end main()
}//end class ObjSer01

The following fragment shows the beginning of the MyProcess class. The constructor for this class contains the code to illustrate object serialization.

The constructor begins by opening an ObjectOutputStream as discussed earlier, and invoking the writeObject() method to write three objects to that stream. The objects written to the stream are a String object, a Date object, and an object of type MyOuterClass described above. Then the constructor flushes and closes the stream.

class MyProcess{
  MyProcess(){//constructor
    try{
      FileOutputStream fout = new FileOutputStream("tmp");
      ObjectOutputStream  outStream  =  
                             new  ObjectOutputStream(fout);
      //write a String object
      outStream.writeObject("Today is ");
      //write a Date object
      outStream.writeObject(new Date());
      //write a custom object
      outStream.writeObject(new MyOuterClass());
      outStream.flush();
      outStream.close();

Than an ObjectInputStream linked to the file containing the three objects is instantiated as shown in the following fragment.

      FileInputStream fin = new FileInputStream("tmp");
      ObjectInputStream inStream = 
                                new ObjectInputStream(fin);

Then the application uses the readObject() method to read the three objects from the stream. In each case the contents of the objects are displayed after they are read. This is illustrated in the following fragment. Note that I omitted the exception handling code from this fragment in the interest of brevity.

        //read String object
        String stringObj = (String)inStream.readObject();
        //display the String object
        System.out.print(stringObj);

        //read the Date object
        Date dateObj = (Date)inStream.readObject();
        //display the Date object
        System.out.println(dateObj);

        //read the custom object
        MyOuterClass myCustomObject = 
                       (MyOuterClass)inStream.readObject();
        //display the custom object
        myCustomObject.showAllData();

The important point is that all three objects were written as objects and read as objects. No effort was required on the part of the programmer to decompose the objects for writing or to reconstruct the objects after reading.

The output from running the program in one particular case is shown in the comments at the beginning of the program.

Program Listing

A listing of the program follows.

/*File ObjSer01.java, Copyright 1997, R.G.Baldwin
Rev 12/17/98
Designed to be compiled and run under JDK 1.1 or a later
version.
This application discusses the object serialization 
capability in JDK 1.1.

This application writes three objects to an output file, 
closes the file, opens the file, reads the objects, and 
displays their contents.

The three objects written to the output file are:
 - A String object
 - A Date object
 - A composite object of a new serializable class named 
   MyOuterClass which contains an embedded object of 
   another new serializable class named MyEmbeddedClass.

The object of the class named MyOuterClass contains two 
private instance variables and a public instance method 
named ShowAllData.

The first private instance variable is a simple String 
object.

The second private instance variable is an embedded object
of another new serializable class named MyEmbeddedClass.

The embedded object also contains a private instance 
variable of type String and a public instance method named
showEmbeddedData().

The application then closes the file and opens it for 
reading.It reads and displays all three objects.

The important point is that all three objects are written 
as objects and read as objects.  No effort was required on
the part of the programmer to decompose the objects for 
writing or to reconstruct the objects after reading.

The output from running the program in one particular case
was:
Today is Thu Dec 17 09:28:58 CST 1998
I am a Private Instance Variable in the Outer Object
I am a Private Instance Variable in the Embedded Object

This program was tested using JDK 1.1 under Win95. It
was also tested using JDK 1.2 under Win95.
**********************************************************/
import java.io.*;
import java.util.*;
//=======================================================//
/*Objects of the following serializable class contain one 
  private instance variable and one public instance method.
  The private instance variable is a simple String object.
  The public instance method is a show method that displays
  the private instance variable.
*/
class MyEmbeddedClass implements Serializable{
  private String myEmbeddedString = "I am a " +
        "Private Instance Variable in the Embedded Object";
  public void showEmbeddedData(){
    System.out.println(myEmbeddedString);
  }//end showEmbeddedData  
}//end class MyEmbeddedClass
//=======================================================//

/*Objects of the following serializable class contain two 
 private instance variables and one public instance method.
 The first instance variable is a simple String object.  
 The second instance variable is an embedded object of a 
 new serializable class which also contains a private 
 instance variable and a public instance method.

 The public instance method in an object of this class is 
 a show method that displays the private String variable 
 in the object and invokes the public show method of the 
 private embedded object to cause the private instance 
 variable in that object to also be displayed.

Note that the class implements the Serializable interface.
*/
class MyOuterClass implements Serializable{
  private String myOuterString = "I am a " +
      "Private Instance Variable in the Outer Object";
  MyEmbeddedClass myEmbeddedObject = new MyEmbeddedClass();

  public void showAllData(){
    System.out.println(myOuterString);
    myEmbeddedObject.showEmbeddedData();
  }//end showAllData()  

}//end class MyOuterClass
//=======================================================//

//Controlling Class
class ObjSer01{
  static public void main(String[] args){
  MyProcess myProcess = new MyProcess();  
  }//end main()
}//end class ObjSer01
//=======================================================//
class MyProcess{
  MyProcess(){//constructor
    try{
      FileOutputStream fout = new FileOutputStream("tmp");
      ObjectOutputStream  outStream  =  
                             new  ObjectOutputStream(fout);
      //write a String object
      outStream.writeObject("Today is ");
      //write a Date object
      outStream.writeObject(new Date());
      //write a custom object
      outStream.writeObject(new MyOuterClass());
      outStream.flush();
      outStream.close();
      
      FileInputStream fin = new FileInputStream("tmp");
      ObjectInputStream inStream = 
                                new ObjectInputStream(fin);
      try{
        //read String object
        String stringObj = (String)inStream.readObject();
        //display the String object
        System.out.print(stringObj);

        //read the Date object
        Date dateObj = (Date)inStream.readObject();
        //display the Date object
        System.out.println(dateObj);

        //read the custom object
        MyOuterClass myCustomObject = 
                       (MyOuterClass)inStream.readObject();
        //display the custom object
        myCustomObject.showAllData();
        
      }catch(ClassNotFoundException e){
        System.out.println(e);
      }//end catch block
      inStream.close();  
    }catch(IOException e){System.out.println(e);}
  }//end constructor
}//end class MyProcess
//=======================================================//

-end-