Chapter 17

Java Serialization

Imagine a building that, with a push of a button, can be turned into a pile of construction materials and the possessions of its residents. Push another button and the building is re-created in its original form in a different location. This is what Java serialization is about. By “pushing the serialize button” you turn an instance of an object into a pile of bytes, and “pushing the deserialize button” magically re-creates the object.

Wikipedia defines the term serialization as

the process of converting a data structure or object into a sequence of bits so that it can be stored in a file or memory buffer, or transmitted across a network connection link to be “resurrected” later in the same or another computer environment. When the resulting series of bits is reread according to the serialization format, it can be used to create a semantically identical clone of the original object… This process of serializing an object is also called deflating or marshalling an object. The opposite operation, extracting a data structure from a series of bytes, is deserialization (which is also called inflating or unmarshalling).

In Lesson 16 you became familiar with streams that deal with single bytes, characters, Java primitives, and text. Now you’ll see why and how to write objects into streams, or how to serialize Java objects.

Consider the following scenario: ClassA creates an instance of the object Employee, which has the fields firstName, lastName, address, hireDate, salary, etc. The values of these fields (that is, the object’s state) have to be saved in a stream. Later on ClassB, which needs these data, somehow has to re-create the object Employee in memory. The instances of ClassA and ClassB may live in two different JVMs running on different computers.

Sure enough, you can remember the order and the data types of firstName, lastName, address, hireDate, and salary, and loop through the fields performing the output with DataOutputStream.

Here’s another use case: Your application has a Preferences menu where the user can select fonts, colors, and UI controls that should be displayed on the opening view. To support such functionality, the Preferences panel creates an instance of a class, UserPreferences, with 50 configurable properties (some of which are of custom data types) that have to be saved in a local file. On the next start of the application the previously saved data should be read from this file with the re-creation of the UserPreferences object.

Writing a manual procedure that uses, say, DataOutputStream on each primitive property and then recursively performs the same operation for each non-primitive type is tedious. Besides, this code would need to be changed each time the properties of class UserPreferences change.

You can achieve the same result in a more elegant way, not one property at a time, but one object at a time, with the help of such streams as ObjectOutputStream and ObjectInputStream. This process is known as Java serialization, which enables you to save objects in a stream in one shot.

ClassA will serialize Employee, and ClassB will deserialize (reconstruct) this object. To support this mode the class Employee has to implement the Serializable interface, as shown in Listing 17-1.

download.eps

Listing 17-1: Serializable class Employee

class Employee implements java.io.Serializable{
     String lName;
     String  fName;
     double salary;
     
}

The interface Serializable does not define any methods, so the class Employee has nothing to implement. If an interface doesn’t declare any methods it’s called a marker interface.

Two use cases described in this section represent the main uses for Java serialization — distribution of the Java objects between different JVMs and object persistence for future reuse. Often serialization is implemented internally by various Java frameworks to pass objects between tiers of a distributed application that runs on multiple servers and client computers.

Some Java frameworks use serialization to load or unload objects to free the JVM’s memory (these are known as passivation and activation of objects, respectively). Later in the book you’ll learn about RMI and EJB, which use serialization a lot. But even in regular business application development you may need to serialize and deserialize application-specific objects. This process comes down to reading from and writing into streams, namely java.io.ObjectInputStream and java.io.ObjectOutputStream, respectively.

The Class ObjectOutputStream

To serialize an object perform the following actions:

1. Open an output stream.

2. Chain it with ObjectOutputStream.

3. Call the method writeObject(), providing the instance of a Serializable object as an argument.

4. Close the stream.

Listing 17-2 shows a code sample that serializes an instance of the Employee object into the file c:practicalJavaBestEmployee.ser. This code first opens FileOutputStream and then chains it with ObjectOutputStream. The method writeObject() performs the serialization of the Employee instance in one shot.

download.eps

Listing 17-2: Serializing an Employee object into a file

import java.io.*;
 
class ClassA {
 
  public static void main(String args[]){
       Employee emp = new Employee();
       emp.lName = "John";
       emp.fName = "Smith"; 
       emp.salary = 50000;
       
       FileOutputStream fOut=null;
       ObjectOutputStream oOut=null;
    
   try{   
     fOut = new  FileOutputStream("c:\practicalJava\BestEmployee.ser");
 
     oOut = new ObjectOutputStream(fOut);
 
     oOut.writeObject(emp);  //serializing employee
 
   } catch (IOException e){
 
       // close the streams 
       try{ 
          oOut.flush();
          oOut.close();
          fOut.close();
       }catch (IOException ioe){
                      ioe.printStackTrace();
       }
   }  
 
   System.out.println("The Employee object has been serialized into " +  
                    "c:\practicalJava\BestEmployee.ser");
  } 
}

All Java primitive data types are serializable. All member variables of the serializable class must be either Java primitives or reference variables pointing to objects that are also serializable. The class Employee from Listing 17-1 contains only serializable data types. But if a class has properties of custom data types, each of them has to be serializable. The Employee class from Listing 17-3 can be serialized only if the class PromotionHistory (and all data types it uses) was declared with implements Serializable.

download.eps

Listing 17-3: Class Employee with custom data types

class Employee implements java.io.Serializable{
     String lName;
     String  fName;
     double salary;
 
     PromotionHistory promos; // A custom data type 
    
}

If you do not want to serialize some sensitive information, such as salary, declare this variable using the keyword transient. If you declare the property salary of the class Employee with the transient qualifier, its value won’t be serialized.

transient double salary;

Typically, you declare a variable as transient either if it contains some sensitive information or if its value is useless in the destination stream. Suppose a Java class that runs on the server has database connectivity variables. If such a class gets serialized to the client’s machine, sending database credentials may be not only useless, but unsafe. So such variables have to be declared as transient. One more reason to use the transient keyword is to serialize an object that includes properties of non-serializable types.

The Class ObjectInputStream

To deserialize an object perform the following steps:

1. Open an input stream.

2. Chain it with the ObjectInputStream.

3. Call the method readObject() and cast the returned object to the class that is being deserialized.

4. Close the stream.

Listing 17-4 shows a code sample that reads the previously serialized file BestEmployee.ser. This code opens a FileInputStream that points at BestEmployee.ser and then chains the FileInputStream with ObjectInputStream. The method readObject() resurrects the instance of the Employee object in memory.

download.eps

Listing 17-4: Deserializing Employee from file

import java.io.*;
class ClassB {
 
  public static void main(String args[]){
       
    FileInputStream fIn=null;
    ObjectInputStream oIn=null;
 
    try{   
     fIn = new  FileInputStream("c:\practicalJava\BestEmployee.ser");
 
     oIn = new ObjectInputStream(fIn);
 
     Employee bestEemp=(Employee)oIn.readObject();
     
   } catch (ClassNotFoundException cnf){
             cnf.printStackTrace();
   } catch (IOException e){
          
       try{ 
         oIn.close();
         fIn.close();
       }catch (IOException ioe){
         ioe.printStackTrace();
       }
   }  
 
   System.out.println("The Employee  object has been deserialized from " +  
                               " c:\practicalJava\BestEmployee.ser");
  } 
}

Note that the class that deserializes an object has to have access to its declaration or the ClassNotFoundException will be thrown. During the process of deserialization all transient variables will be initialized with the default values for their type. For example, int variables will have a value of 0.

Keep in mind that class variables with longer names produce larger footprints when the object is serialized. In time-critical applications this may be important. Imagine a Wall Street trading system in which each order is presented as an object, TradeOrder, with 50 properties, and you need to send a thousand of these objects over the network using serialization. Simply shortening each variable name by one character can reduce the network traffic by almost 50 KB! You’ll learn how to open the network streams in Lesson 18.

The Interface Externalizable

Continuing with the example of the trade order, the knowledge of the values of all 50 fields from the class TradeOrder may be required to support certain functionality of the application, but when the program has to send a buy or sell request to the stock exchange, only ten properties may be needed. This raises a question — can the process of serialization be customized so only some of the object properties will be serialized?

The method writeObject() of ObjectOutputStream sends all the object’s properties into a stream. But if you want to have more control over what is being serialized and decrease the footprint of your object, implement the Externalizable interface, which is a subclass of Serializable.

This interface defines two methods: readExternal() and writeExternal(). These methods have to be written by you to implement serialization of only the selected properties. Listing 17-5 shows a fragment of a class, Employee2, that implements Externalizable. It has several properties, but in a certain scenario only id and salary have to be serialized.

download.eps

Listing 17-5: Externalizable version of Employee

class Employee2 implements java.io.Externalizable {
       String lName;
       String fName;
       String address;
       Date hireDate;  
       int id;     
       double salary;
     
   public void writeExternal(ObjectOutputStream stream) 
                               throws java.io.IOException {
    // Serializing only the salary and id  
    stream.writeDouble(salary); 
    stream.writeInt(id);
   }
 
   public void readExternal(ObjectInputStream stream)
                               throws java.io.IOException {
     salary = readDouble();  // Order or reads must be the 
                             // same as the order of writes
     id  = readInt();
   }
  
}

Note that each property of the class is individually serialized and the methods writeExternal() and readExternal() must write and read properties, respectively, in the same order. The class EmpProcessor from Listing 17-6 shows how to externalize the object Employee2.

download.eps

Listing 17-6: Turning an object into an array of bytes

import java.io.*;
import java.util.Date;
 
public class EmpProcessor {
 
      public static void main(String[] args) {
              
           Employee2 emp = new Employee2();
           emp.fName = "John";
           emp.lName = "Smith"; 
           emp.salary = 50000;
           emp.address = "12 main street";
           emp.hireDate = new Date();
           emp.id=123;  
    
           FileOutputStream fOut=null;
           ObjectOutputStream oOut=null;
             
             try{
               fOut= new FileOutputStream( 
                  "c:\practicalJava\NewEmployee2.ser");
               
               oOut = new ObjectOutputStream(fOut);
               emp.writeExternal(oOut);  //serializing employee
               
            System.out.println("An employee is serialized into " +
                         " c:\practicalJava\NewEmployee2.ser");
 
             }catch(IOException e){
                         e.printStackTrace(); 
             }finally{
                    try {
                              oOut.flush();
                              oOut.close();
                              fOut.close();
                    } catch (IOException e1) {
                              e1.printStackTrace();
                    }
             }
     }
}

You had to write a little more code to implement the Externalizable interface than to implement the Serializable interface, but the size of the file NewEmployee2.ser will be substantially smaller. First of all, you serialized the values of only two properties, and files created with the Externalizable interface contain only data, while files created with Serializable also contain class metadata that includes properties’ names. The code snippet in Listing 17-7 shows you how to re-create the externalized object Employee2.

download.eps

Listing 17-7: Re-creating externalized object

fIn= new FileInputStream("c:\practicalJava\NewEmployee2.ser");
oIn = new ObjectInputStream(fIn);
 
Employee2 emp = new Employee2();
emp.readExternal(oIn);

Class Versioning

Imagine that a program, ClassA, serializes the Employee object from Listing 17-1 into a file on Mary’s computer. Two days later Mary starts another program, ClassB, that offers a download of its new version with long-awaited features. After upgrade, ClassB starts generating errors, which are caused by the fact that the new upgrade includes a modified version of the class Employee that now has one property with a different data type than what exists in memory. The upgrade also includes one new property that wasn’t in the previous version of the Employee object.

Now the deserialization process will try to ignore the new property but will fail because of the mismatched property types between the serialized and in-memory versions of Employee. Serialization may also fail because of a change in the inheritance tree of Employee.

During serialization, JVM automatically calculates a special value: the serial version unique ID, which is based on the properties of the serializable object, the class name, the implemented interfaces, and the signatures of non-private methods. If you are curious to see how this number looks for your class, run the program serialver (it’s located in the bin directory of your Java install), providing the name of your class as a command-line argument.

But if your class explicitly defines and initializes a static final variable called serialVersionUID, Java will use your value instead of trying to generate one. For example,

Public static final  serialVersionUID = 123; 

Now, if you keep the value of this variable in the new version of Employee the same as in the old one, you’ll have some freedom to add more methods to this class, and JVM will assume that both classes have the same version. If a new version has added a public field, the deserialization process will ignore it. If a new version has removed a field, the deserialized version will still have it initialized with a default value. But if you change the data type for the public field, the deserialization process will fail anyway.

Serializing into Byte Arrays

An interesting effect will be produced if you serialize your objects into an in-memory array of bytes — byte[]. This can be the easiest way of creating a sort of memory blob that can be exchanged among different VMs.

The syntax of such serialization is pretty straightforward. Let’s assume that you have a class called XYZ that implements Serializable and contains all the elements of your report in the proper format. To prepare a byte array from it, write the code in Listing 17-8.

download.eps

Listing 17-8: Turning an object into an array of bytes

XYZ myXyz = new XYZ();
 
// Code to fill myXyz with the content goes here
 
//...
 
ByteArrayOutputStream baOut = new ByteArrayOutputStream(5000);
ObjectOutputStream oOut = new ObjectOutputStream(
                               new BufferedOutputStream(baOut));
 
//Here comes the serialization part
 
oOut.writeObject(myXyz);
oOut.flush();
 
// create a byte array from the stream
 
byte[] xyzAsByteArray = baOut.toByteArray();
oOut.close();

Another convenient use for serializing into a byte array is object cloning, which is the creation of an exact copy of an object instance. Even though the class Object, the root of all classes, includes the method clone(), it works only with objects that implement the Cloneable marker interface; otherwise the cloning will fail. Things may get more complicated when an object contains instances of other objects: You need to program a deep copy of the object.

Serialization into a byte array with immediate deserialization creates a deep copy of the object in no time. After executing the code from Listing 17-9 you’ll get two identical objects in memory. The variable bestEmployee points at one of them, and the variable cloneOfBestEmployee points at another.

download.eps

Listing 17-9: Object cloning with serialization

Employee bestEmployee = new Employee();
 
//Serialize into byte array
ByteArrayOutputStream baOut = new ByteArrayOutputStream();
ObjectOutputStream oOut = new ObjectOutputStream(baOut);
oos.writeObject(bestEmployee);
 
//Deserialize from byte array
ByteArrayInputStream baIn = new ByteArrayInputStream(baOut.toByteArray());
ObjectInputStream oIn = new ObjectInputStream(baIn);
Employee cloneOfBestEmployee = (Employee) oin.readObject();
 

Try It

Create a Java Swing program called MyCustomizableGUI that will enable the user to specify her preferences, such as background color, font family, and size. Selected values should be assigned to properties of the serializable class UserPreferences and stored in the file preferences.ser. Each time MyCustomizableGUI is started it should determine if the file preferences.ser exists. If the file does exist, MyCustomizableGUI should deserialize it and apply previously saved preferences to the GUI.

Lesson Requirements

You should have Java installed.

note.ai

You can download the code and resources for this Try It from the book’s web page at www.wrox.com. You can find them in the Lesson17 folder in the download.

Step-by-Step

1. Create a new Eclipse project and name it Lesson17.

2. Create an executable Swing class called MyCustomizableGUI with a text field and a User Preferences button.

3. Program the button to open a new window Preferences (based on JDialog), that has three drop-downs (aka JComboBox), and Save and Cancel buttons. The first drop-down will allow the user to select a color, the second a font, and the third a font size.

4. Create a serializable class, UserPreferences, that will remember the user’s selections. When the user has made her choices, the Save button has to serialize the instance of UserPreferences into a file named preferences.ser.

5. Each time MyCustomizableGUI starts it has to determine if the file preferences.ser exists, deserialize it if so, and apply the appropriate color as the background color of the window MyCustomizableGUI. The font preferences should be applied to the text field.

6. Run the program to ensure that you can change and save the preferences and that they are properly applied to the GUI.

cd.ai

Please select Lesson 17 on the DVD with the print book, or watch online at www.wrox.com/go/fainjava to view the video that accompanies this lesson.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.145.56.28