Java Serialization - Wrox Press Java Programming 24-Hour Trainer 2nd (2015)

Wrox Press Java Programming 24-Hour Trainer 2nd (2015)

Lesson 15. 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 translating data structures or object state into a format that can be stored (for example, in a file or memory buffer, or transmitted across a network connection link) and reconstructed 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 Chapter 14 you became familiar with streams that deal with single bytes, characters, Java primitives, and text. Now you 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, and so on. The values of these fields (that is, the object’s state) have to be saved in a stream. Later, ClassB, which needs these data, somehow has to re-create the instance of the object Employee in memory. The instances of ClassA and ClassB may live in two different Java Virtual Machines (JVMs) running on different computers.

Sure enough, your program can memorize the order of the fields and their data types of firstName, lastName, address, hireDate, and salary, and loop through the fields performing the output with DataOutputStream. The program that reads this stream needs to know the fields' order and their types.

Here’s another use case: Your application has a Preferences menu where the user can select fonts, colors, and user interface (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.

NOTE This lesson discusses only core Java serialization. But the process of converting an instance of Java into XML, JSON, or another text format is also referred to as serialization. You see an example of serialization into JSON and XML formats in Lesson 33 on RESTFul Web Services.

ClassA serializes the object Employee, and ClassB deserializes (reconstructs) this object. To support this mode, the class Employee has to be serializable—that is, it has to implement the Serializable interface, as shown in Listing 15-1.

Listing 15-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. The marker interfaces are just affecting the way compiler generates the bytecode.

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 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 15-2 shows a code sample that serializes an instance of the Employee object into the file c:\practicalJava\BestEmployee.ser. This code first opens FileOutputStream and then chains it with ObjectOutputStream. The method writeObject() performs the serialization of the Employeeinstance in one shot.

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

class ClassA {

public static void main(String args[]){

Employee emp = new Employee();

emp.lName = "John";

emp.fName = "Smith";

emp.salary = 50000;

try (FileOutputStream fOut =

new FileOutputStream("BestEmployee.ser");

ObjectOutputStream oOut =

new ObjectOutputStream(fOut);){

oOut.writeObject(emp); //serializing employee

} catch (IOException ioe){

ioe.printStackTrace();

}

System.out.println(

"Employee object has been serialized into 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 15-1 contains only serializable data types. But if a class has fields of custom data types, each of them has to be serializable. The Employee class from Listing 15-3 can be serialized only if the class PromotionHistory (and all data types it uses) was declared with implements Serializable.

Listing 15-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 field salary of the class Employee with the transient qualifier, its value won’t be serialized. The serialized set of bytes will still contain the transientfield, but it’s not going to be the actual value; such a field will have a default value for its type.

transient double salary;

Typically, you declare a variable as transient 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. Therefore, such variables have to be declared as transient. One more reason to use the transient keyword is to serialize an object that includes fields 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 15-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 Employeeobject in memory.

Listing 15-4: Deserializing Employee from file

import java.io.*;

class ClassB {

public static void main(String args[]){

try ( FileInputStream fIn =

new FileInputStream("BestEmployee.ser");

ObjectInputStream oIn = new ObjectInputStream(fIn);){

Employee bestEmp=(Employee)oIn.readObject();

}catch (ClassNotFoundException cnf){

cnf.printStackTrace();

} catch (IOException ioe){

ioe.printStackTrace();

}

System.out.println(

"The Employee object has been deserialized.");

}

}

Note that the class that deserializes an object has to have access to its declaration or the ClassNotFoundException is thrown. During the process of deserialization all transient variables are initialized with the default values for their type. For example, int variables 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 fields, and you need to send 1,000 of these objects over the network using serialization. Simply shortening each field name by one character can reduce the network traffic by almost 50 KB! You find out how to open the network streams in Chapter 16.

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 fields may be needed.

This raises a question—can the process of serialization be customized so only some of the object fields are serialized?

The method writeObject() of ObjectOutputStream sends all the object’s fields 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 fields. Listing 15-5 shows a fragment of a class, Employee2, that implements Externalizable. It has several fields, but in a certain scenario only id and salary have to be serialized.

Listing 15-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(ObjectOutput stream)

throws java.io.IOException {

// Serializing only the salary and id

stream.writeDouble(salary);

stream.writeInt(id);

}

public void readExternal(ObjectInput stream)

throws java.io.IOException {

salary = stream.readDouble(); // Order of reads must be the

// same as the order of writes

id = stream.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 15-6 shows how to externalize the object Employee2.

Listing 15-6: Externalizing an Employee object

import java.io.*;

import java.util.Date;

public class ClassAExt {

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;

try ( FileOutputStream fOut=

new FileOutputStream("NewEmployee2.ser");

ObjectOutputStream oOut =

new ObjectOutputStream(fOut);){

emp.writeExternal(oOut); //externalizing employee

System.out.println(

"An employee is externalized into NewEmployee2.ser");

}catch(IOException ioe){

ioe.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 theExternalizable interface contain only data, whereas files created with Serializable also contain class metadata that includes properties’ names. The ClassBExt in Listing 15-7 shows you how to re-create in memory the externalized object Employee2.

Listing 15-7: Re-creating the externalized object

public class ClassBExt {

public static void main(String[] args) {

try (FileInputStream fIn=

new FileInputStream("NewEmployee2.ser");

ObjectInputStream oIn = new ObjectInputStream(fIn);){

Employee2 emp = new Employee2();

emp.readExternal(oIn);

System.out.println("Deserialized employee with id "

+ emp.id);

// format the output as dollars

System.out.printf("salary = $%7.2f", emp.salary);

}catch (IOException ioe){

ioe.printStackTrace();

}

}

}

Class Versioning

Imagine that a program, ClassA, serializes the Employee object from Listing 15-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 tries to ignore the new property but fails 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 uses 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 have some freedom to add more methods to this class, and JVM assumes that both classes have the same version. If a new version has added a public field, the deserialization process ignores it. If a new version has removed a field, the deserialized version still has it initialized with a default value. But if you change the data type for the public field, the deserialization process fails anyway.

Serializing into Byte Arrays

You can also serialize 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 virtual machines (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 15-8.

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

XYZ myXyz = new XYZ();

// Code to assign values to the fields of myXyz 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 another marker interface Cloneable; otherwise the cloning fails. 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 15-9 you get two identical objects in memory. The variable bestEmployee points at one of them, and the variable cloneOfBestEmployee points at another.

Listing 15-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 to clone the object

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 enables the user to specify her preferences, such as background color, font family, and size. Pick any GUI components that have these attributes. Selected values should be assigned to fields of the serializable classUserPreferences and be serialized into 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 You can download the code and resources for this “Try It” from the book’s web page at www.wrox.com/go/javaprog24hr2e. You can find them in the Lesson15.zip.

Step-by-Step

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

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-down menus (JComboBox), a Save button, and a Cancel button. The first drop-down menu enables the user to select a color, the second a font, and the third a font size.

4. Create a serializable class, UserPreferences, that remembers 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.

TIP Please select the videos for Lesson 15 online at www.wrox.com/go/javaprog24hr2e. You will also be able to download the code and resources for this lesson from the website.