Sunday, 2 November 2014

Serialization

Serialization is the action to save the state of an object into a specific format, like for example a string.

In order to be serializable, a class should implement the interface "Serializable".
eg.
 class Person implements Serializable {
    // serializable class' version number
    private static final long serialVersionUID = 1L; 

    String name;
    int age;
 }

The class "ObjectOutputStream" has to be used in order to serialize an object.
eg.
 // The object to serialize
 Person person = new Person();
 person.name = "Joe";
 person.age = 19;

 // We serialize the "person" object
 try{
    FileOutputStream fos = new FileOutputStream("/tmp/person.ser");
    ObjectOutputStream os = new ObjectOutputStream(fos);
 
    // Serialization of the "person" object
    os.writeObject(person);
 
    // We close the stream
    os.close();

 }catch(java.io.IOException ex){
    System.out.println("Serialization failed: "+ex.getMessage());
 }


Deserialization


Deserialization is the action to restore the state of a serialized object.

The class "ObjectInputStream" has to be used in order to deserialize an object.
eg.
 // We deserialize the file "person.ser"
 try{
    FileInputStream fis = new FileInputStream("/tmp/person.ser");
    ObjectInputStream os = new ObjectInputStream(fis);

    try{
       Person person = (Person)os.readObject();
 
       // We display the object's info
       System.out.println("Person's name: "+person.name);
       System.out.println("Person's age: "+person.age);

    }catch(ClassNotFoundException ex){
       System.out.println("Deserialization failed while reading object: "+ex.getMessage());
    }

    // We close the stream
    os.close();

 }catch(java.io.IOException ex){
    System.out.println("Deserialization failed: "+ex.getMessage());
 }

Output:
Person's name: Joe
Person's age: 19


Serialization and deserialization of many objects and primitives


Note that the classes "ObjectOutputStream" and "ObjectInputStream" allow you to serialize and deserialize one or many objects and primitives in the same file. The order in which the objects and primitives are serialized matter!

Example of serialization of many objects and primitives:
 // A person object
 Person person = new Person();
 person.name = "Joe";
 person.age = 19;
 
 // We serialize the "person" object
 try{
    FileOutputStream fos = new FileOutputStream("/tmp/objectsAndPrimitives.ser");
    ObjectOutputStream os = new ObjectOutputStream(fos);
 
    // The order in which we will write objects and primitives is important

    // Serialization of the "person" object
    os.writeObject(person);
 
    // Serialization of a string
    os.writeObject("This is a string");

    // We add few primitives
    os.writeInt(199);
    os.writeBoolean(true);

    // We close the stream
    os.close();

 }catch(java.io.IOException ex){
    System.out.println("Serialization failed: "+ex.getMessage());
 }

Example of deserialization of many objects and primitives:

Note that the order in which the objects and primitives have been serialized matter.
 // We deserialize the file "objectsAndPrimitives.ser"
 try{
    FileInputStream fis = new FileInputStream("/tmp/objectsAndPrimitives.ser");
    ObjectInputStream os = new ObjectInputStream(fis);

    try{
       // We have to call the methods "read*" (like "readObject()", "readInt()", ..) in the same order
       // than when we used the methods "write*" (like "writeObject()", "writeInt()", ..) (see the previous example)

       // We deserialize the "person" object: it has been serialized as first object
       Person person = (Person)os.readObject();
 
       // We deserialize the String object: it has been serialized as second object
       String aString = (String)os.readObject();

       // The primitives
       int anInteger = os.readInt();
       boolean aBoolean = os.readBoolean();
 
       // We display person's info
       System.out.println("Person's name: "+person.name);
       System.out.println("Person's age: "+person.age);
 
       // The serialized string
       System.out.println("string: "+aString);
 
       // The primitives
       System.out.println("int: "+anInteger);
       System.out.println("boolean: "+aBoolean);

    }catch(ClassNotFoundException ex){
       System.out.println("Deserialization failed while reading object: "+ex.getMessage());
    }

    // We close the stream
    os.close();

 }catch(java.io.IOException ex){
    System.out.println("Deserialization failed: "+ex.getMessage());
 }


Serialization of a graph of objects


When you serialize an object, this object may use the references of other objects in its implementation. In this case JVM (Java Virtual Machine) will try to serialize the entire "graph of objects" that the main object to serialize depends of. If one of these objects, part of the objects graph, does not implement the interface "Serializable" then the compilation succeed but a runtime exception will be thrown!

Eg.
 // The class "Car" is not Serializable
 class Car { 
    String model;
    int yearProduction;
 
    Car(String pModel, int pYearProduction){
       this.model = pModel;
       this.yearProduction = pYearProduction;
    }
 }
 
 class Person implements Serializable {
    private static final long serialVersionUID = 1L;
 
    String name;
    int age;

    Car personalCar;
    }

    // The object to serialize
    Person person = new Person();
    person.name = "Joe";
    person.age = 19;

    // A "Person" HAS-A "Car"
    person.personalCar = new Car("Renault 19", 1996);

    // We serialize the "person" object
    try{
       FileOutputStream fos = new FileOutputStream(FILE_NAME);
       ObjectOutputStream os = new ObjectOutputStream(fos);
 
    // Serialization of the "person" object
    os.writeObject(person);
 
    // We close the stream
    os.close();

 }catch(java.io.IOException ex){
    System.out.println("Serialization failed: ");
    ex.printStackTrace();
 }
Compile fine but a runtime Exception is thrown:
java.io.NotSerializableException: Car


Transient keyword


In the previous example, the class "Car" is not serializable. If you can not modify the code source of the class "Car" in order to make it serializable, you can still declare the instance variable 'personalCar' as transient. This will prevent JVM to try serializing the instance of "Car" when you serialize an object of the class "Person".

Eg.
 class Person implements Serializable {
    private static final long serialVersionUID = 1L;
 
    String name;
    int age;
    transient Car personalCar;
 }

If you serialize an object of the class "Person", JVM will not serialize the value of the instance 'personalCar', instead it will set it as "null".


The privates methods "readObject" and "writeObject"


If you can manually save the state of an object which is not serializable with using the following methods:
private void writeObject(ObjectOutputStream os) : it is automatically called by JVM during an object serialization
private void readObject(ObjectInputStream os) : it is automatically called by JVM during the deserialization to an object

Let's take the example of the "Person" class. This class has a reference to a Car. As "Car" class is not serializable, we marked the instance variable 'personnalCar' as transient.

Eg.
 class Person implements Serializable {
    private static final long serialVersionUID = 1L;
 
    String name;
    int age;
    transient Car personalCar;
 }


What we want is to manually save the state of the object "Car" during the serialization of a "Person" object. To achieve this we have to add the private method 'writeObject' in the class Person:
 class Person implements Serializable {
    private static final long serialVersionUID = 1L;
 
    String name;
    int age;
    transient Car personalCar;
 
    private void writeObject(ObjectOutputStream os) {
       try {
          os.defaultWriteObject(); // Serialize the state of the current Person object (except transient instance variables and static variables)
 
          String carModel = null;
          int carYearProduction = 0;
 
          // We set the state of the "Car" object, if this object exists
          if(this.personalCar != null){
             carModel = this.personalCar.model;
             carYearProduction = this.personalCar.yearProduction;
          }
 
         // We manually save the state of a "Car" object
         os.writeObject(carModel);
         os.writeInt(carYearProduction);
 
      } catch(Exception ex) {
         System.out.println("Serialization in the private method 'writeObject' has failed: ");
         ex.printStackTrace();
      }
 }

During the deserialization of a Person object, we want to manually create an instance of a Car object and set it to the instance variable 'personalCar'. To achieve this we have to add the private method 'readObject' in the class Person:
 class Person implements Serializable {
    private static final long serialVersionUID = 1L;
 
    String name;
    int age;
    transient Car personalCar;

    private void readObject(ObjectInputStream os) {
       try{

          os.defaultReadObject(); // Deserialize the state of the current "Person" object

          // We get the state of a "Car" object
          String carModel = (String)os.readObject();
          int carYearProduction = os.readInt();

          // We create a new "Car" object
          if(carModel != null){
             this.personalCar = new Car(carModel, carYearProduction);
          }

       }catch(Exception ex){
          System.out.println("Deserialization in the private method 'readObject' has failed: ");
          ex.printStackTrace();
       }
   }

   private void writeObject(ObjectOutputStream os) {
      try{
         // Serialize the state of the current "Person" object 
         // (except transient instance variables and static variables)
         os.defaultWriteObject();
 
         String carModel = null;
         int carYearProduction = 0;
 
         // We set the state of the "Car" object, if this object exists
         if(this.personalCar != null){
            carModel = this.personalCar.model;
            carYearProduction = this.personalCar.yearProduction;
         }
 
         // We manually save the state of "Car" object
         os.writeObject(carModel);
         os.writeInt(carYearProduction);
 
      }catch(Exception ex){
         System.out.println("Serialization in the private method 'writeObject' has failed: ");
         ex.printStackTrace();
      }
   }

 }

With the new implementation of the "Person" class, each time a "Person" object is serialized and deserialized, the state of the "Car" object will be maintained.


Serialization an inheritance


If a superclass implements "Serializable" then all of its subclass do too.
If a superclass does not implements "Serializable" then when a subclass's object is deserialized the unserializable superclass's constructor runs!

Therefore, if a class "Child" has a superclass "Parent" which is not serializable and if you serialize an object of "Child" then the state of "Parent" will not be serialized! During the deserialization of an object of "Child" the constructor of the superclass "Parent" will run! Therefore make sure that a superclass is serializable. In fact make sure that the entire inheritance tree is serializable.


Version number of a "serializable" class


From Java's API:

The serialization runtime associates with each serializable class a version number, called a serialVersionUID, which is used during deserialization to verify that the sender and receiver of a serialized object have loaded classes for that object that are compatible with respect to serialization. If the receiver has loaded a class for the object that has a different serialVersionUID than that of the corresponding sender's class, then deserialization will result in an InvalidClassException. A serializable class can declare its own serialVersionUID explicitly by declaring a field named "serialVersionUID" that must be static, final, and of type long:

ANY-ACCESS-MODIFIER static final long serialVersionUID = 42L;
If a serializable class does not explicitly declare a serialVersionUID, then the serialization runtime will calculate a default serialVersionUID value for that class based on various aspects of the class, as described in the Java(TM) Object Serialization Specification. However, it is strongly recommended that all serializable classes explicitly declare serialVersionUID values, since the default serialVersionUID computation is highly sensitive to class details that may vary depending on compiler implementations, and can thus result in unexpected InvalidClassExceptions during deserialization. Therefore, to guarantee a consistent serialVersionUID value across different java compiler implementations, a serializable class must declare an explicit serialVersionUID value. It is also strongly advised that explicit serialVersionUID declarations use the private modifier where possible, since such declarations apply only to the immediately declaring class--serialVersionUID fields are not useful as inherited members. Array classes cannot declare an explicit serialVersionUID, so they always have the default computed value, but the requirement for matching serialVersionUID values is waived for array classes.

Source: http://docs.oracle.com/javase/8/docs/api/java/io/Serializable.html