Thursday, 5 July 2012

Serialization in java


Serialization is the process of saving an object state in a storage medium (such as a file, or a memory buffer) or to transmit it over a network connection in binary form.The object can be restored (Deserialization) at a later time, and even a later location. With persistence, we can move an object from one computer to another, and have it maintain its state.

In general there are three approaches to serialization in java:

  • Implement Serializable and use default protocol.
  • Implement Serializable and get a chance to modify the default protocol.
  • Implement Externalizable and write your own protocol to implement serialization.

The default serialization mechanism by implementing Serializable interface, here you dont have to do much,just implement Seralizable marker interface in your class and invoke readObject and writeObject methods of DataInputStream and DataOutputStream for serialization and deserialization.

Unlike Serializable interface, Externalizable interface is not a marker interface and it provides two methods - writeExternal and readExternal. These methods are implemented by the class to give the class a complete control over the format and contents of the stream for an object and its supertypes.

In this tutorial we will discuss about the default serialization mechanism by implemeting Serializable interface.To learn about Externalization please visit Externalization in Java.

The default serialization mechanism


The object to be persisted must implement the marker interface java.io.Serializable or inherit that implementation from its object hierarchy.The serialization interface has no methods or fields and serves only to inform the compiler that this java class can be serialized.Classes that do not implement this interface will not have any of their state serialized or deserialized.All subtypes of a serializable class are themselves serializable.

The default serialization mechanism for an object writes the class of the object, the class signature, and the values of all non-transient and non-static fields. References to other objects (except in transient or static fields) cause those objects to be written also. Multiple references to a single object are encoded using a reference sharing mechanism so that graphs of objects can be restored to the same shape as when the original was written. Serialization transform a graph of Java objects into an array of bytes for storage or transmission.The graph of objects means starting from a single object, until all the objects that can be reached from that object by following instance variables, are also serialized.This includes the super class of the object until it reaches the "Object" class and the same way the super class of the instance variables until it reaches the "Object" class of those variables.

 Here are two rules concerning persistent objects:

Rule 1: The object to be persisted must implement the Serializable interface or inherit that implementation from its object hierarchy

Rule 2: The object to be persisted must mark all nonserializable fields transient


We can use writeObject(Object obj) method of java.io.ObjectOutputStream class for writing graph of serializable objects to a byte stream.

Similarly the readObject() method of java.io.ObjectInputStream class can be used to recover those objects previously serialized.


Default Serialization Example


In this example a serializable Employee object is saved and restored using the default serialization mechanism.


package serialize;

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 DefaultSerializeExample {

 /**
  * @param args
  * @throws ClassNotFoundException
  **/
 public static void main(String[] args) throws ClassNotFoundException {

  ObjectOutputStream out = null;
  ObjectInputStream in = null;
  try {
   out = new ObjectOutputStream(new FileOutputStream("employee.ser"));

   /** Create one Employee object to serialise **/

   Employee emp = new Employee(12345, "Banking");

   /** Serialise the object emp and write to file employee.ser **/

   out.writeObject(emp);

   in = new ObjectInputStream(new FileInputStream("employee.ser"));

   /** Deserialise the object stored in the file employee.ver **/

   Employee emp_copy = (Employee) in.readObject();

   System.out.println(emp_copy);

  } catch (IOException e) {
   e.printStackTrace();
  } finally {
   try {
    out.close();
    in.close();
   } catch (IOException e) {
    e.printStackTrace();
   }
  }
 }

}

/** Serializable class **/

class Employee implements Serializable {

 /**
  * Generated serialVersionUID for versioning of serialised objects.
  * 
  **/
 private static final long serialVersionUID = -417056492237332874L;

 int empno;
 String dept;

 public Employee(int empno, String dept) {

  this.empno = empno;
  this.dept = dept;
 }

 public Employee() {

 }

 /** Override the toString method to print object fields **/

 public String toString() {
  return (" Employee No =" + empno + " & Department =" + dept);
 }

}
The output will be

 Employee No =12345 & Department =Banking


Customize Default Serialization Protocol


Classes that require special handling during the serialization and deserialization process must implement special methods with these exact signatures:

 private void writeObject(java.io.ObjectOutputStream out)
     throws IOException

 private void readObject(java.io.ObjectInputStream in)
     throws IOException, ClassNotFoundException;


Notice that both methods are declared private and ofcourse they must be declared private, proving that neither method is inherited and overridden or overloaded. The virtual machine will automatically check to see if either method is declared during serialization and deserialization.If available the custom readObject(ObjectInputStream) and writeObject(ObjectOutputStream) will be used else use the default serialization mechanism. The virtual machine can call private methods of your class whenever it wants but no other objects can.


You can write your custom code inside these two methods and customize the standard behaviour of serialization.


private void writwObject(ObjectInputStream in)
             throws IOException{


  in.defaultWriteObject();      //Serialize the component in the usual way.


  /* Write your custom code here */
}


This method calls the defaultWriteObject() method of the ObjectOutputStream to serialize the object as normal, and then takes care of the post-processing it needs to perform.Similarly



private void readObject(ObjectInputStream in)
             throws IOException, ClassNotFoundException {


  in.defaultReadObject();      // Deserialize the component in the usual way.


  /* Write your custom code here */
}

This method calls the defaultReadObject() method of the ObjectInputStream to deserialize the object as normal, and then takes care of the post-processing it needs to perform.

Let us see one example:

Custom Serialization Example:


In this example the serializable Employee class inheriting a non-serializable class Person.In the default serialization mechanism only the fields of Serializable objects are written out to the stream. You must customize the serialization of your subclass with writeObject(ObjectOutputStream) and readObject(ObjectInputStream) in order to write out and restore any information from the non-serializable superclass that you find necessary.
In this example we are declaring writeObject and readObject methods inside the serializable sub class Employee for explicitly saving and restoring the non-serializable parent class field 'empName' .

package serialize;

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 CustomSerialization {

 /**
  * @param args
  * @throws ClassNotFoundException
  **/
 public static void main(String[] args) throws ClassNotFoundException {

  ObjectOutputStream out = null;
  ObjectInputStream in = null;
  try {
   out = new ObjectOutputStream(new FileOutputStream("employee.ser"));

   /** Create one Employee object to serialise **/

   Employee emp = new Employee("John", 12345, "Banking");

   /** Serialise the object emp and write to file employee.ser **/

   out.writeObject(emp);

   in = new ObjectInputStream(new FileInputStream("employee.ser"));

   /** Deserialise the object stored in the file employee.ver **/

   Employee emp_copy = (Employee) in.readObject();

   System.out.println(emp_copy);

  } catch (IOException e) {
   e.printStackTrace();
  } finally {
   try {
    out.close();
    in.close();
   } catch (IOException e) {
    e.printStackTrace();
   }
  }
 }

}

/** Non-serializable parent class **/

class Person {

 String empName;

 /**
  * mandatory public non-arg constructor. It will be invoked during
  * deserialization process.
  **/
 public Person() {
  System.out.println("Deserialization:Invoking non-arg Person() constructor");
 }

 public Person(String empName) {
  this.empName = empName;
 }

 /** Override the toString method to print object fields **/

 public String toString() {
  return ("Employee Name =" + empName);
 }
}

/** Serializable sub class **/

class Employee extends Person implements Serializable {

 /**
  * Generated serialVersionUID for versioning of serialised objects.
  * 
  **/
 private static final long serialVersionUID = -417056492237332874L;

 int empno;
 String dept;

 public Employee(String empName, int empno, String dept) {
  super(empName);
  this.empno = empno;
  this.dept = dept;
 }

 public Employee() {

 }

 /** Override the toString method to print object fields **/

 public String toString() {
  return ("Employee Name =" + empName + "Employee No =" + empno
    + " & Department =" + dept);
 }

 /**
  * Declared to customize the deserialization process. The nonserialized
  * super class field 'empName' will be restored explicitly.
  **/

 private void readObject(ObjectInputStream in) throws IOException,
   ClassNotFoundException {

  // Restore the sub class fields.
  in.defaultReadObject();

  System.out
    .println("Deserialization: Restoring Non-Serializable parent class field explicitly.");
  
  // explicitly do the restoring for super class field.
  empName = in.readUTF();

 }

 /**
  * Declared to customize the serialization process. The nonserialized super
  * class field 'empName' will be saved explicitly.
  **/

 private void writeObject(ObjectOutputStream out) throws IOException {

  // Save the sub class fields.
  out.defaultWriteObject();

  System.out
    .println("Serialization: Saving Non-Serializable parent class field explicitly.");

  // explicitly do the saving for super class field.
  out.writeUTF(empName);
 }

}

The output will be

Serialization: Saving Non-Serializable parent class field explicitly.
Deserialization: Invoking non-arg Person() constructor 
Deserialization: Restoring Non-Serializable parent class field explicitly.
Employee Name =John Employee No =12345 & Department =Banking

You can see that value of the non-serializable parent class field 'empName' saved and restored successfully.


Create Your Own Protocol: the Externalizable Interface


The third option for serialization is create your own protocol with the Externalizable interface.To learn more about externalization please visit Externalization in Java

java.io.NotSerializableException


Please visit  java.io.NotSerializableException in Java to learn about this topic.

Versioning of serializable objects


The serialVersionUID is used for the versioning of serializable objects.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.

Please visit serialVersionUID in Java Serialization to learn more about serialVersionUID.

How Inheritance Affects Serialization


All subtypes of a serializable class are themselves serializable.An object is serializable (itself implements the Serializable interface) even if its super class is not.Please visit Serializing an object which has a non-serializable parent class to learn more about it.

Using Serialization


Serialization is a mechanism built into the core Java libraries for writing a graph of objects into a stream of data. This stream of data can then be programmatically manipulated, and a deep copy of the objects can be made by reversing the process. This reversal is often called deserialization.

In particular, there are three main uses of serialization:
As a persistence mechanism

If the stream being used is FileOutputStream, then the data will automatically be written to a file.

As a copy mechanism (Deep cloning)

If the stream being used is ByteArrayOutputStream, then the data will be written to a byte array in memory. This byte array can then be used to create duplicates of the original objects.

As a communication mechanism

If the stream being used comes from a socket, then the data will automatically be sent over the wire to the receiving socket, at which point another program will decide what to do.
The important thing to note is that the use of serialization is independent of the serialization algorithm itself. If we have a serializable class, we can save it to a file or make a copy of it simply by changing the way we use the output of the serialization mechanism.

No comments:

Post a Comment