Java Serialization

 

Serialization is the process of converting a Java object into bytes. Let’s say, we initialized a Java object, set its state and we want to save it for later use with its current state. The most straightforward way for this would be to note the values of the object’s fields somewhere and use these values once again to set the fields of the object later on once we initialize it again. This is manual work and open to human errors. Instead, using Java Serialization, we can convert the object with its current state into a sequence of bytes, save it on the disk and retrieve it back from the disk whenever we want to use it again.

Serialization is one of the most important mechanisms provided by the Java programming language. It constitutes the basis for persisting Java objects. Another very important use of Serialization is when Java objects are transmitted between different JVMs over network connections. Java Serialization makes it possible to send the object over the wire with its current state.

The process of constituting a Java object back from its bytes is called deserialization. For example, when a serialized object is sent to a different JVM with a RMI call, it is first deserialized back into its object form, and then it is used in the new JVM.

java.io.Serializable interface

An object is said to be serializable if its class implements the java.io.Serializable interface. Serializable objects are not required to implement any specific methods. Implementing java.o.Serializable is just a way for a Java class to tell Java runtime that the objects initialized from it can be serialized into bytes for purposes like being saved on the disk.

public class MyClass implements java.io.Serializable {

}

As a best practice, it is convenient to define an instance variable named serialVersionUID, of type long, for a serializable class. serialVersionUID is used by the Serialization Runtime as a version number while the class is serialized. When the serialized class is deserialized, serialVersionUID is used as the version of the class. If during deserialization, it is determined that the version of the deserialized class is different than that of the serialized one, an InvalidClassException is thrown. A serialVersioUID variable is automatically assigned to a class that implements java.io.Serializable by the JVM. However, the developer can declare the serialVersionUID explicitly in the class as a best practice. The declaration of serialVersionUID is as follows.

< public | private | protected | <default> > static final long serialVersionUID = <any long value>;

An example declaration for a staticVersionUID might be as follows.

private static final long serialVersionUID = 1L;

Not declaring a serialVersionUID for a serializable class causes the Java compiler to give a warning but does not cause a compiler error. It is a recommended practice to declare the serialVersionUID for a class that implements java.io.Serializable.

Serializing and Deserializing Objects

Java provides two classes to use while serializing and deserializing object: java.io.ObjectInputStream and java.io.ObjectOutputStream. Both of these classes are higher-level classes. That means, they need to be wrapped around lower classes when they are used. Most of the time, java.io.FileInputStream is used as the lower-level class for java.io.ObjectInputStream and java.io.FileOutputStream is used as the one for java.io.ObjectOutputStream.

The following methods are used in serialization and deserialization.

Object ObjectInputStream.readObject(String pathToFile) throws IOException for deserializing an object

void ObjectOutputStream.writeObject(String pathToFile) throws IOException, ClassNotFoundException for serializing an object

Now, let’s see how serialization and deserialization works with an example.

First, let’s define a class that implements serializable.

import java.io.Serializable;

public class Student implements Serializable {

private static final long serialVersionUID = 1L;

private String firstName;

private String lastName;

private String grade;

public Student(String firstName, String lastName, String grade) {

this.firstName = firstName;

this.lastName = lastName;

this.grade = grade;

}

}

Now, let’s initialize an object from the Student class, serialize it, then deserialize it and get its state.

import java.io.FileInputStream;

import java.io.FileOutputStream;

import java.io.IOException;

import java.io.ObjectInputStream;

import java.io.ObjectOutputStream;

public class MyClass {

public static void main(String [] args) {

Student student = new Student(“George”, “Marshall”, “1st”);

FileOutputStream fileInputStream = null;

ObjectOutputStream objectOutputStream = null;

try {

fileOutputStream = new FileOutputStream(“student.obj”);

objectOutputStream = new ObjectOutputStream(fileInputStream);

objectOutputStream.writeObject(student);

} catch (IOException e) {

System.out.println(“Problem while serializing student.”);

e.printStackTrace();

System.exit(1);

} finally {

try {

objectOutputStream.close();

} catch (IOException e) {

e.printStackTrace();

System.exit(1);

}

}

FileInputStream fileInputStream = null;

ObjectInputStream objectInputStream = null;

try {

fileInputStream = new FileInputStream(“student.obj”);

objectInputStream = new ObjectInputStream(fileInputStream);

Student deserializedStudent = (Student) objectInputStream.readObject();

System.out.println(“Student’s information, name:” + deserializedStudent.getFirstName() +

” ” + deserializedStudent.getLastName() + “, grade: ” + student.getGrade());

} catch (IOException e) {

System.out.println(“Problem while deserializing student.”);

e.printStackTrace();

System.exit(1);

} catch (ClassNotFoundException e) {

System.out.println(“The file cannot be found.”);

e.printStackTrace();

System.exit(1);

} finally {

try {

objectInputStream.close();

} catch (IOException e) {

e.printStackTrace();

System.exit(1);

}

}

}

}

Inside the main() method of MyClass, we first initialize a Student object with firstName as George, lastName as Marshall and grade as !st. Following that, the Student object is serialized. The serialization is done by first initializing a FileOutputStream pointing to somewhere on the hard drive of the computer, then wrapping an ObjectOutputStream around it and finally calling writeObject() method of the ObjectOutputStream. Now, the Student object’s bytes are saved on the hard drive, in a file named student.obj. In the second part of the main method, a new Student object is constructed by deserializing the bytes of the Student object defined in the first part. For that, we first define a FileInputStream pointing to student.obj file on the hard drive. Then, we wrap an ObjectInputStream around it and call readObject() method ObjectInputStream. This constructs a new Student object which we assign to the Student variable named deserializedStudent. Then we print the Student’s state on the console.

Because readObject() and writeObject() methods throw Exception, we handle these exceptions when those methods are called. Also, it’s a good practice to close ObjectInputStream and ObjectOutputStream objects once we are done with them. Therefore, in the main method, we close the ObjectInputStream and ObjectOutputStream objects that we initialized before inside a finally block, to guarantee that they are closed. This also throws an IOException, which we handle by catching.

One thing we need to pay attention to while deserializing an object is that, readObject() method returns an Object. We need to cast the result of the readObject() call to the appropriate object. In this example, the result the readObject() call inside the main() method of MyClass is cast to a Student object.

The subclasses of a serializable class are serializable. If a serializable class’ parent class is non-serializable, the child class’ fields that inherit their values from the parent class may get affected. For example, if a field inherited from the parent class is set to value1 in the parent class’ constructor but then set to value2 in the child class’ constructor, its value will be value1 when an object of type child class is serialized and then deserialized. The reason for this is that, when the object is initialized, first the parent’s constructor runs and then the child’s constructor runs, setting the value of the field to value1. However, because parent is not serializable, when the child class’ object is reconstructed from bytes, the parent class’ constructor will run and the field inherited from the parent will get its value from the parent class. Any instance variable that is inherited from a non-serializable parent will get its value from the parent’s constructor, dismissing the part that changed its value in the serializable child class.

Serializing Member Classes

Serialization process fails if a Serializable class has member classes that are not Serializable. If one or more of the member classes of a Serializable class do not implement java.io.Serializable, then ObjectOutputStream.writeObject() call fails with a java.io.NotSerializable exception. java.io.NotSerializable exception is a runtime exception and does not need to be caught or declared.

As an example, let’s define a new Serializable class, with a member class that is not Serializable.

public class Text {

private String paperText;

private String color;

public Text(String paperText, String color) {

this.paperText = paperText;

this.color = color;

}

public String getPaperText() {

return paperText;

}

public String getColor() {

return color;

}

}

import java.io.FileOutputStream;

import java.io.IOException;

import java.io.ObjectOutputStream;

import java.io.Serializable;

public class Paper implements Serializable {

private static final long serialVersionUID = 1L;

private double width;

private double height;

private Text text;

public Paper(double width, double height, Text text) {

this.width = width;

this.height = height;

this.text = text;

}

public void getPaperInfo() {

System.out.println(text.getPaperText() + ” is written on a paper with dimensions ” + width + “x”

+ height + ” in ” + text.getColor() + ” color.”);

}

public static void main(String[] args) {

Text text = new Text(“Hello”, “blue”);

Paper paper = new Paper(8.27, 11.69, text);

FileOutputStream fileOutputStream = null;

ObjectOutputStream objectOutputStream = null;

try {

fileOutputStream = new FileOutputStream(“C:\\java_examples\\serialized_objects\\paper.obj”);

objectOutputStream = new ObjectOutputStream(fileOutputStream);

objectOutputStream.writeObject(paper);

}

catch (IOException e) {

System.out.println(“Problem while serializing paper.”);

e.printStackTrace();

System.exit(1);

}

finally {

try {

objectOutputStream.close();

}

catch (IOException e) {

e.printStackTrace();

System.exit(1);

}

}

}

}

Running Paper class would end up with the following output.

Problem while serializing paper.

java.io.NotSerializableException: Text

at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1184)

at java.io.ObjectOutputStream.defaultWriteFields(ObjectOutputStream.java:1548)

at java.io.ObjectOutputStream.writeSerialData(ObjectOutputStream.java:1509)

at java.io.ObjectOutputStream.writeOrdinaryObject(ObjectOutputStream.java:1432)

at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1178)

at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:348)

at Paper.main(Paper.java:36)

The output states that the Text object is not Serializable. The fix is easy; make Text class Serializable. So making Text class Serializable as follows fixes the problem.

import java.io.Serializable;

public class Text implements Serializable {

//The following is optional.

private static final long serialVersionUID = 1L;

}

Now, Paper class can be serialized and deserialized without any problems.

transient keyword

If we want to exclude an instance variable from the serialization process when an object is serialized, we declare it with the transient keyword. This way, when the object is persisted to bytes, no bytes for the value of a transient variable is added to the sequence of bytes generated by the object. In other words, a transient variable is never serialized. Instance variables declared transient are still member variables of a class and they follow the rules for non-transient member variables. A transient variable gets its default value when the object is constructed back from its bytes through deserialization.

Now let’s see how transient variables work with an example. For this, we will define a serializable class with a transient instance variable and see what the behavior is when an object initialized from that class is serialized and then deserialized. Let’s define a Ball class which has a color and a radius.

import java.io.FileInputStream;

import java.io.FileOutputStream;

import java.io.IOException;

import java.io.ObjectInputStream;

import java.io.ObjectOutputStream;

import java.io.Serializable;

public class Ball implements Serializable {

private static final long serialVersionUID = 1L;

private double radius;

private transient String color;

public Ball(double radius, String color) {

this.radius = radius;

this.color = color;

}

public double getRadius() {

return radius;

}

public String getColor() {

return color;

}

public static void main(String [] args) {

Ball ball = new Ball(2.0, “yellow”);

System.out.println(“Before serialization…”);

System.out.println(“radius: ” + deserializedBall.getRadius());

System.out.println(“color:” + deserializedBall.getColor());

FileOutputStream fileOutputStream = null;

ObjectOutputStream objectOutputStream = null;

try {

fileOutputStream = new FileOutputStream(“ball.obj”);

objectOutputStream = new ObjectOutputStream(fileOutputStream);

objectOutputStream.writeObject(ball);

} catch (IOException e) {

System.out.println(“Problem while serializing ball.”);

e.printStackTrace();

System.exit(1);

} finally {

try {

objectOutputStream.close();

} catch (IOException e) {

e.printStackTrace();

System.exit(1);

}

}

FileInputStream fileInputStream = null;

ObjectInputStream objectInputStream = null;

try {

fileInputStream = new FileInputStream(“ball.obj”);

objectInputStream = new ObjectInputStream(fileInputStream);

Ball deserializedBall = (Ball) objectInputStream.readObject();

System.out.println(“After serialization…”);

System.out.println(“radius: ” + deserializedBall.getRadius());

System.out.println(“color: ” + deserializedBall.getColor());

} catch (IOException e) {

System.out.println(“Problem while deserializing ball.”);

e.printStackTrace();

System.exit(1);

} catch (ClassNotFoundException e) {

System.out.println(“The file cannot be found.”);

e.printStackTrace();

System.exit(1);

} finally {

try {

objectInputStream.close();

} catch (IOException e) {

e.printStackTrace();

System.exit(1);

}

}

}

}

The output would be

Before serialization…

radius: 2.0

color: yellow

After serialization…

radius: 2.0

radius: null

Even though the color field of the Ball object had value “yellow” before serialization, its value is null after the object is deserialized. The reason for this is that color is a String type field and a String object’s default value is null.

%d bloggers like this: