Wednesday, May 28, 2014

Serialization in Java


Serialization is the process of converting an object's state (including its references) to a sequence of bytes, as well as the process of rebuilding those bytes into a live object at some future time.

Sections in this post: 
  1. Some uses of serailization
  2. Serializable Interface
  3. Serialization steps
  4. readObject and writeObjectMethods
  5. FAQ

Serialization is used when you want to persist the object. It is also used by RMI to pass objects between JVMs, either as arguments in a method invocation from a client to a server or as return values from a method invocation. In general, serialization is used when we want the object to exist beyond the lifetime of the JVM. 


Here are some uses of serialization
  • To persist data for future use.
  • To send data to a remote computer using such client/server Java technologies as RMI or socket programming.
  • To "flatten" an object into array of bytes in memory.
  • To exchange data between applets and servlets.
  • To store user session in Web applications.
  • To activate/passivate enterprise java beans.
  • To send objects between the servers in a cluster. 

Java provides Serialization API, a standard mechanism to handle object serialization. To persist an object in java, we need to follow following steps.
  1.  the first step is to flatten the object. For that the respective class should implement "java.io.Serializable" interface. We don't need to implement any methods as this interface do not have any methods. This is a marker interface/tag interface. Marking a class as Serializable indicates the underlying API that this object can be flattened. 


import java.io.Serializable;
import java.util.Calendar;
import java.util.Date;

public class MyDateObject implements Serializable{

    private static final long serialVersionUID = -5315058568373987829L;
    private Date date;

    public MyDateObject() {
        date= Calendar.getInstance().getTime();
    }

    public Date getDate() {
        return date;
    }

    public void setDate(Date date) {
        this.date=date;
     }
}

     2.  Next step is to actually persist the object. To persist an object we need to use node stream to write to file systems or transfer a flattened object across a network. We can use java.io.ObjectOutputStream class for this. So to write an object you use "writeObject(<<instance>>)" method of "java.io.ObjectOutputStream" class and to read an object you use "readObject()" method of "java.io.ObjectOutputStream" class.

Note:  "readObject()" can read only serialized object, that means if the class does not implement "java.io.Serializable" interface, "readObject()" cannot read that object.


import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;


//Class to persist the time in a flat file time.txt
public class WriteSerializeClass {

  public static void main(String [] args) {
       String filename = "c://time.txt";
       if(args.length > 0){
          filename = args[0];
       }       

      MyDateObject time = new MyDateObject();
      FileOutputStream fos = null;
      ObjectOutputStream out = null;

      try{

          fos = new FileOutputStream(filename);
          out = new ObjectOutputStream(fos);
          out.writeObject(time);
          out.close();
      }catch(IOException ex){
          ex.printStackTrace();
      }
   }
}

    

 
import java.io.FileInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.util.Calendar;
//Class to read the time from a flat file time.txt
public class ReadSerializeClass   {

  public static void main(String [] args) {
         String filename = "c://time.txt";

         if(args.length > 0){
             filename = args[0];
         }
   
         MyDateObject time = null;
         FileInputStream fis = null;
         ObjectInputStream in = null;

         try{
             fis = new FileInputStream(filename);
             in = new ObjectInputStream(fis);
             time = (MyDateObject)in.readObject();
             in.close();
         }catch(IOException ex){
             ex.printStackTrace();
         }catch(ClassNotFoundException cnfe){
             cnfe.printStackTrace();
         }

         // print out restored time
         System.out.println("Restored time: " + time.getDate());

         // print out the current time
         System.out.println("Current time: " 
                      + Calendar.getInstance().getTime());
      }
  }

readObject and writeObject methods:

 To enhance the normal process of serialization/de-serialization provide two methods inside your serializable class. Those methods are:
  1. private void writeObject(ObjectOutputStream out) throws IOException;
  2. private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException; 
   Let's look at one example

Without readObjet()/writeObject()

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.Calendar;
import java.util.Date;

public class MyDateObject implements Serializable{
    
 
    private static final long serialVersionUID = -5315058568373987829L;
    private Date date;

    public MyDateObject() {
        //date= Calendar.getInstance().getTime();
        calculateCurrentTime();
    }

    public Date getDate() {
        return date;
    }

    public void setDate(Date date) {
     this.date=date;
     } 
    
    private void calculateCurrentTime(){
        date = Calendar.getInstance().getTime();
    }
 }
Output:
=================================================================

 Restored time: Wed May 28 15:54:31 IST 2014
Current time: Wed May 28 15:54:34 IST 2014

Now we will add the two methods:  readObjet()/writeObject()


import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.Calendar;
import java.util.Date;

public class MyDateObject implements Serializable{    
 
   private static final long serialVersionUID = -5315058568373987829L;
   private Date date;

    public MyDateObject() {
        //date= Calendar.getInstance().getTime();
        calculateCurrentTime();
    }

    public Date getDate() {
        return date;
    }

    public void setDate(Date date) {
        this.date=date;
     } 
    
    private void calculateCurrentTime(){
        date = Calendar.getInstance().getTime();
    }    //Adding writObject()
    private void writeObject(ObjectOutputStream out) throws IOException {
        out.defaultWriteObject();
    }      //Adding readObject()
    private void readObject(ObjectInputStream in) 
                         throws IOException, ClassNotFoundException{

         in.defaultReadObject();
        // now perfrom same operation you need to do in constructor
        calculateCurrentTime();
    }
}

Output:
=========================================================================
Restored time: Wed May 28 16:08:26 IST 2014
Current time: Wed May 28 16:08:26 IST 2014

 So by overriding these two methods, we can easily get desired serialization/de-serialization behavior.

Note:  serialization does not care about access modifiers. It serializes all private, public and protected fields.

Again there is one more way to serialize the object - create your own protocol with the Externalizable interface. Instead of implementing the Serializable interface, you can implement Externalizable, which contains two methods:
  1. public void writeExternal(ObjectOutput out) throws IOException; 
  2. public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException;
The Externalization is discussed as separate post. Check it out here .

Some FAQ:
  1. why readObject And writeObject declared as private?
     Ans:  We don't want these methods to be overridden by subclasses. Instead, each class can have its own writeObject method, and the serialization engine will call all of them one after the other. This is only possible with private methods (these are not overridden). (The same is valid for readObject.)
That's why  both methods are declared private . The trick here is that the virtual machine will automatically check to see if either method is declared during the corresponding method call. The virtual machine can call private methods of your class whenever it wants but no other objects can. Thus, the integrity of the class is maintained and the serialization protocol can continue to work as normal. 

 2. How stop from serailizing one of the sub class of a serializable class?

Ans:To stop the automatic serialization, we can once again override the readObject/writeObject  methods to just throw the NotSerializableException in our class.


private void writeObject(ObjectOutputStream out) throws IOException{

    throw new NotSerializableException("Dont Serialize");
}

private void readObject(ObjectInputStream in) throws IOException{

    throw new NotSerializableException("Dont Serialize");
}

     





No comments :

Post a Comment