Wednesday, 12 April 2017

Serialization in Java

Object serialization is the mechanism of converting object into byte stream. Where as the reverse process of constituting the object from those bytes is known as deserialization.

Use of serialization

Once an object is converted to a byte stream those bytes can be -

  1. Transmitted from one JVM to another where the object can be reconstituted using those bytes and used in further processing.
  2. Stored on a disk/DB for further use.
Serialization in Java
Serialization in Java

Portability

One important feature of serialization is the portability it offers. When a serialized object is transmitted across the network the serialization mechanism will take into account the differences in operating systems. Thus an object converted into byte streams in Windows OS can be transmitted across the network and deserialized into an object in the Unix operating system.

Serializable interface

A mandatory requirement for a class object to be able to be serialized is that the class should implement Serializable interface which is part of java.io package.

Here note that Serializable is a marker interface and does not have any field or method.

Steps involved in Serialization of an object

In order to serialize an object you need an object of ObjectOutputStream and an object of OutputStream to be wrapped inside ObjectOutputStream object. Then you need to call writeObject() method which will serialize your object and send it to OutputStream.

Steps involved in Deserialization of an object

In order to deserialize an object you need an object of ObjectInputStream and an object of InputStream to be wrapped inside ObjectInputStream object. Then you need to call readObject() method which will reconstitute your object from the byte stream.

Example code

Let’s see an example of serialization, here we have an Address bean with fields like addressline1, city etc. and a Person bean that along with other fields also has an instance of address class. That way you can see that the whole object graph is serialized.

Then we have Util class with the methods to serialize and deserialize an object. Also the test class SerializationDemo to execute the code.

Address class

public class Address implements Serializable{
 private String addressLine1;
 private String addressLine2;
 private String city;
 private String state;
 private String country;
 
 Address(String addressLine1, String addressLine2, String city, String state, String country){
  this.addressLine1 = addressLine1;
  this.addressLine2 = addressLine2;
  this.city = city;
  this.state = state;
  this.country = country;
 }
 public String getAddressLine1() {
  return addressLine1;
 }

 public String getAddressLine2() {
  return addressLine2;
 }
 
 public String getCity() {
  return city;
 }
 
 public String getState() {
  return state;
 }
 
 public String getCountry() {
  return country;
 }

}

Person class

public class Person implements Serializable{

 /**
  * 
  */
 private static final long serialVersionUID = 1L;
 private String name;
 private Date dob;
 // transient
 private transient int age;
 private Address address;
 // Constructor
 Person(String name, Date dob, int age, Address address){
  System.out.println("In Constructor with args");
  this.name = name;
  this.dob = dob;
  this.age = age;
  this.address = address;
    
 }
 // no-arg Constructor
 Person(){
  System.out.println("no-arg constructor");
 }
 
 public Address getAddress() {
  return address;
 }

 public String getName() {
  return name;
 }
 
 public Date getDob() {
  return dob;
 }
 
 public int getAge() {
  return age;
 }
}

Util class

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;

public class Util {
 
 /**
  * Method used for serialization
  * @param obj
  * @param fileName
  */
 public static void serialzeObject(Object obj, String fileName){
  try(ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(new File(fileName)))){
   oos.writeObject(obj);
   
  } catch (FileNotFoundException e) {
   // TODO Auto-generated catch block
   e.printStackTrace();
  } catch (IOException e) {
   // TODO Auto-generated catch block
   e.printStackTrace();
  }
 }
 
 /**
  * Method used for deserializing
  * @param fileName
  * @return
  * @throws ClassNotFoundException
  */
 public static Object deSerialzeObject(String fileName) throws ClassNotFoundException{
  Object obj = null;
  try(ObjectInputStream ois = new ObjectInputStream(new FileInputStream(new File(fileName)))){
   obj = ois.readObject();
   
  } catch (FileNotFoundException e) {
   // TODO Auto-generated catch block
   e.printStackTrace();
  } catch (IOException e) {
   // TODO Auto-generated catch block
   e.printStackTrace();
  }
  return obj;
 }

}

SerializationDemo class

import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;


public class SerializationDemo {
 public static void main(String[] args) {
  DateFormat df = new SimpleDateFormat("dd/MM/yyyy");
  Date dob = null;
  try {
   dob = df.parse("04/04/2015");
  } catch (ParseException e1) {
   // TODO Auto-generated catch block
   e1.printStackTrace();
  }
  // Creating and initializaing a Address object
  Address address = new Address("#34", "ABC Street", "New Delhi", "Delhi", "India");
  // Creating and initializaing a Person object
  Person person = new Person("User1", dob, 2, address);
  // file name
  final String fileName = "D://person.ser";
  // serializing
  Util.serialzeObject(person, fileName);
  
  try {
   // deserializing
   person = (Person)Util.deSerialzeObject(fileName);
  } catch (ClassNotFoundException e) {
   // TODO Auto-generated catch block
   e.printStackTrace();
  }
  System.out.println("deserialized object ---- ");
  System.out.println("Name " + person.getName());
  System.out.println("DOB " + person.getDob());
  System.out.println("Age " + person.getAge());
  System.out.println("Address Line1 " + person.getAddress().getAddressLine1());
  System.out.println("Address Line2 " + person.getAddress().getAddressLine2());
  System.out.println("City " + person.getAddress().getCity());
  System.out.println("State " + person.getAddress().getState());
  System.out.println("Country " + person.getAddress().getCountry());
  
  
 }

}

Output

In Constructor with args
deserialized object ---- 
Name User1
DOB Sat Apr 04 00:00:00 IST 2015
Age 0
Age #34
Age ABC Street
Age New Delhi
Age Delhi
Age India

Here you specify the file name and then pass the person object and the file name to the serializeObject method of the Util class which will do the process of serialization. Again you pass the filename which already has the stored bytes for the person object to the deSerializeObject method of the Util class which will deserialize the serialized person object.

Points to note here

  • Here you can see in person object there is a reference to address object and both are serialized which shows the whole object graph gets serialized even if there is a very complex hierarchy.
  • All the classes which are to be serialized should be implementing Serializable interface. In this example suppose Address class doesn’t implement Serializable interface and there is a reference of it in Person class then you will get exception when you try to serialize the person object.
    java.io.NotSerializableException: org.test.Address
  • If you have noticed in Person class there is a system.println in both with args and no-arg constructors. When the object is deserialized you can see even default no-arg constructor is not called. Though reiterated several times I’ll repeat it again that object is reconstituted from the serialized bytes while deserializing and no constructor is called during the process of deserialization.
  • In Person class age field is marked as transient which stops this field from getting serialized. That’s why you can see age is printed as 0.

    Refer Transient in Java to know more about transient keyword.

writeObject( ) and readObject( ) methods

If you want to have some control over the process of serialization and deserialization rather than using the default serialization mechanism which for us, as end user, is automatic then you can add writeObject() and readObject() methods.

If you add methods writeObject() and readObject(), these methods will be called automatically when the object is serialized and deserialized respectively and provide the serialization mechanism rather than using the default mechanism.

General form

private void writeObject(ObjectOutputStream oos) throws IOException

private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException

Example code

One scenario where these methods can be used quite effectively is in a parent-child relationship where parent class doesn’t implement the serializable interface but you want the fields that are there in the parent class to be serialized too.

Let’s say we have a classA which is the super class and classB which extends classA.

ClassA

public class ClassA {
 private int deptId;
 private String deptName;
 public int getDeptId() {
  return deptId;
 }
 public void setDeptId(int deptId) {
  this.deptId = deptId;
 }
 public String getDeptName() {
  return deptName;
 }
 public void setDeptName(String deptName) {
  this.deptName = deptName;
 }
 
}

ClassB

import java.io.Serializable;

public class ClassB extends ClassA implements Serializable{
 /**
  * 
  */
 private static final long serialVersionUID = 7280011452607087533L;
 private String empId;
 private String empName;
 public String getEmpId() {
  return empId;
 }
 public void setEmpId(String empId) {
  this.empId = empId;
 }
 public String getEmpName() {
  return empName;
 }
 public void setEmpName(String empName) {
  this.empName = empName;
 }
 
}

In this case if you create a ClassB object and serialize it. Then deserializing it won’t give you any value for the fields which ClassB object gets by virtue of extending ClassA.

public class TestSerialize{
 
 public static void main(String[] args) {
  final String fileName = "D://test.ser";
  ClassB objb = new ClassB();
  objb.setDeptId(1);
  objb.setDeptName("Finance");
  objb.setEmpId("E001");
  objb.setEmpName("Ram");
  Util.serialzeObject(objb, fileName);
  try {
   objb = (ClassB)Util.deSerialzeObject(fileName);
  } catch (ClassNotFoundException e) {
   // TODO Auto-generated catch block
   e.printStackTrace();
  }
  System.out.println("DeptId " + objb.getDeptId() + " DeptName " + objb.getDeptName());
  System.out.println("EmpId " + objb.getEmpId() + " EmpName " + objb.getEmpName());
 }

}

Output

DeptId 0 DeptName null
EmpId E001 EmpName Ram

You can see that the deptId and deptName fields which are in ClassA are not serialized as ClassA doesn’t implement Serializable interface.

In this case you will have to add writeObject and readObject methods and provide functionality so that the fields of ClassA are also serialized. So, your updated ClassB with writeObject() and readObject() methods added will look like –

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;

public class ClassB extends ClassA implements Serializable{
 /**
  * 
  */
 private static final long serialVersionUID = 7280011452607087533L;
 private String empId;
 private String empName;
 public String getEmpId() {
  return empId;
 }
 public void setEmpId(String empId) {
  this.empId = empId;
 }
 public String getEmpName() {
  return empName;
 }
 public void setEmpName(String empName) {
  this.empName = empName;
 }
 
 // adding writeObject method
 private void writeObject(ObjectOutputStream oos) throws IOException{
  // calling default functionality for classB fields
  oos.defaultWriteObject();
  // Explicitly writing ClassA fields
  oos.writeInt(getDeptId());
  oos.writeObject(getDeptName());
 }
 
 // adding readObject method
 private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException{
  // calling default functionality for classB fields
  ois.defaultReadObject();
  // Explicitly reading ClassA fields and setting them
  setDeptId(ois.readInt());
  setDeptName((String)ois.readObject());
 }
}

Output

DeptId 1 DeptName Finance
EmpId E001 EmpName Ram

Running it using the TestSerialize class will now give you proper output.

Points to note here

  1. One thing to be careful about here is that the order of reading the fields should be same as the order they were written.
  2. You can use defaultWriteObject() and defaultReadObject() as the first statement in writeObject() and readObject() methods respectively for using the default serialization mechanism.

That's all for this topic Serialization in Java. If you have any doubt or any suggestions to make please drop a comment. Thanks!


Related Topics

  1. Externalizable interface in Java
  2. Transient in Java
  3. serialVersionUID and versioning in Java Serialization
  4. Serialization Proxy Pattern in Java
  5. Marker interface in Java

You may also like -

>>>Go to Java advance topics page

No comments:

Post a Comment