Monday, 17 April 2017

Externalizable interface in Java

Though serialization in Java provides a pretty good default implementation for serializing and deserializing an object. All you need to do is to implement Serializable interface and the whole process is automatic for you.

But, what if you want to control the process of serialization, you have some fields in your object which hold confidential information and you don’t want to serialize those fields or a sub-object with in your object graph doesn’t need to be serialized. That’s when you can use externalizable interface.

Externalizable interface in Java

Externalizable interface extends the Serializable interface (which is a marker interface) and adds two methods writeExternal() and readExternal().

When you use Externalizable for your serialization, you will implement Externalizable interface and implement writeExternal() and readExternal() methods. These two methods will be called while serializing and deserializing respectively.

General form of writeExternal() and readExternal()

  • readExternal(ObjectInput in) - The object implements the readExternal method to restore its contents by calling the methods of DataInput for primitive types and readObject for objects, strings and arrays.
  • writeExternal(ObjectOutput out) - The object implements the writeExternal method to save its contents by calling the methods of DataOutput for its primitive values or calling the writeObject method of ObjectOutput for objects, strings, and arrays.

Refer Serialization in Java to see another way, using readObject and writeObject to control the process of serialization.

Serialization process when Externalizable interface is used

Each object to be stored is tested for the Externalizable interface. If the object supports Externalizable, the writeExternal method is called. If the object does not support Externalizable and does implement Serializable, the object is saved using ObjectOutputStream.

When an Externalizable object is reconstructed, an instance is created using the public no-arg constructor, then the readExternal method called. Serializable objects are restored by reading them from an ObjectInputStream.

Example code

Let’s see an example where we have a class called User in which pwd field is there that you don’t want to convert into bytes. Though that can also be done by marking it as transient but here let us see how Externalizable can be used to control the serialization.

User class

import java.io.Externalizable;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;

public class User implements Externalizable {
 private String userName;
 private int id;
 private String pwd;
 
 // no-arg constructor **Required**
 public User(){
  System.out.println("In no-arg constructor");
 }
 
 public User(String userName, int id, String pwd){
  System.out.println("In constructor with args");
  this.userName = userName;
  this.id = id;
  this.pwd = pwd;
 }
 
 public String getUserName() {
  return userName;
 }

 public int getId() {
  return id;
 }

 public String getPwd() {
  return pwd;
 }

 @Override
 public void writeExternal(ObjectOutput out) throws IOException {
  System.out.println("In writeExternal method");
  out.writeObject(userName);
  out.writeInt(id);
 }

 @Override
 public void readExternal(ObjectInput in) throws IOException,
   ClassNotFoundException {
  System.out.println("In readExternal method");
  userName = (String)in.readObject();
  id = in.readInt();
 
 }
}

ExternalizableDemo class

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

public class ExternalizableDemo {

 public static void main(String[] args) {
  User user = new User("TestUser", 1, "pwd");
  try {
   ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("test.ser"));
   oos.writeObject(user);
   oos.close();
   
   ObjectInputStream ois = new ObjectInputStream(new FileInputStream("test.ser"));

   user = (User)ois.readObject();
   ois.close();
   System.out.println("UserName " + user.getUserName() + " id " 
      + user.getId() + " pwd " + user.getPwd());
   
  } catch (IOException | ClassNotFoundException e) {
   // TODO Auto-generated catch block
   e.printStackTrace();
  }
 }
}

Output

In constructor with args
In writeExternal method
In no-arg constructor
In readExternal method
UserName TestUser id 1 pwd null

Here you can see that writeExternal() and readExternal() methods are called for serializing and deserializing the object. Now it is upto you to provide implementation for serialization in writeExternal() method where pwd field is excluded.

Points to note here are -

  1. A default no-arg constructor has to be there while using externalizable as object is created using no-arg constructor while deserializing and then the object is initialized using the logic in readExternal() method. Note that it is different from default serialization where object is reconstituted using the byte stream and constructor is not called.
  2. Order that is used in writeExternal() method for writing the fields should be maintained in readExternal() method.

Externalizable with inheritance

One scenario where Externalizable 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.

In this case if you create an object of the child class and serialize it using default serialization. Then deserializing it won’t give you any value for the fields which child class object gets by virtue of extending parent class.

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.Externalizable;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;

public class ClassB extends ClassA implements Externalizable{
 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;
 }
 @Override
 public void writeExternal(ObjectOutput out) throws IOException {
  System.out.println("In writeExternal method");
  //Writing parent class ClassA fields
  out.writeInt(getDeptId());
  out.writeObject(getDeptName());
  // Writing child class fields
  out.writeObject(getEmpId());
  out.writeObject(getEmpName());
  
 }

 @Override
 public void readExternal(ObjectInput in) throws IOException,
   ClassNotFoundException {
  System.out.println("In readExternal method");
  // Setting parent class fields
  setDeptId(in.readInt());
  setDeptName((String)in.readObject());
  // Setting child class fields
  setEmpId((String)in.readObject());
  setEmpName((String)in.readObject());
 }

}

Test class

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

public class ExternalizableDemo {

 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");
  try {
   ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(fileName));
   oos.writeObject(objb);
   oos.close();
   
   ObjectInputStream ois = new ObjectInputStream(new FileInputStream(fileName));

   objb = (ClassB)ois.readObject();
   ois.close();
   System.out.println("DeptId " + objb.getDeptId() + " DeptName " + objb.getDeptName() 
     + " EmpId " + objb.getEmpId() + " EmpName "+ objb.getEmpName());
   
  } catch (IOException | ClassNotFoundException e) {
   // TODO Auto-generated catch block
   e.printStackTrace();
  }
 }
}

Output

In writeExternal method
In readExternal method
DeptId 1 DeptName Finance EmpId E001 EmpName Ram

Since you can control what is serialized and how, you can make sure that all the fields of super class ClassA are also serialized and deserialized in the writeExternal() and readExternal() methods.

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


Related Topics

  1. Serialization 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