0
Sponsored Links


Ad by Google
In this tutorial you will learn about serialization, What is serialization? How to implement serialization in Java? When to use serialization? Why serialization? Things to keep in mind while implementing serialization.

OK, so lets start with our first question what is serialization?

Well Serialization is a mechanism to convert object's state into sequence of byte stream, so that you can easily send the object byte stream via network or write them in you hard disk.
Deserialization is the way of getting the actual object back from the serialized form of an object.

To serialized an object your class or any of its super classes must implements either the Serializable interface or it's sub interface called Externalizable. Serialized object has extension called .ser, Serializable is a marker interface means no methods no any fields declared inside the java.io.Serializable interface its an empty interface just to mark the class as this class is required a special treatment.

How to implement serialization in Java?

1. Write a class which must implement java.io.Serializable interface.
2. Use java.io.ObjectOutputStream class to serialized the object.

Example:

package com.javamakeuse.poc;

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

public class SerializableSecret implements Serializable {

 private String secretString;
 private Date time;

 public SerializableSecret(){
  System.out.println("serializing object at "+
    Calendar.getInstance().getTime());
 }
 
 public String getSecretString() {
  return secretString;
 }
 public void setSecretString(String secretString) {
  this.secretString = secretString;
 }
 
 public Date getTime() {
  return time;
 }
 
 public void setTime(Date time) {
  this.time = time;
 }
}
SerializableSecret.java class is ready to serialized by enabling the serialization via implementing the Serializable interface. Next job is of java.io.ObjectOutputStream class to provide the writeObject(obj) method to write the object stream. Here is complete signature of writeObject(obj) method.
public final void writeObject(Object obj) throws IOException

OK, Now lets serialized the object.

package com.javamakeuse.poc;

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

public class SerializationTest {

 final static String fileName = "secret.ser";

 public static void main(String[] args) {

  SerializableSecret secret = new SerializableSecret();
  secret.setSecretString("My Pay-Check");
  
  // serializing object
  serializedObject(secret);

 }

 public static void serializedObject(SerializableSecret secret) {
  FileOutputStream fos;
  ObjectOutputStream out;
  try {
   fos = new FileOutputStream(fileName);
   out = new ObjectOutputStream(fos);

   // Here the writeObject() method of ObjectOutputStream class,
   // will write the byte stream into secret.ser file
   out.writeObject(secret);
   out.close();
  } catch (IOException e) {
   e.printStackTrace();
  }
 }
}
Run the SerializationTest.java class, It will create a serialized object named as secret.ser file. Your object serialization is done, now if you need to get back the copy of the original object from the serialized form(secret.ser) then you have to Deserialized the object. Deserialization is achieve via readObject() method of java.io.ObjectInputStream class, readObject() method will return Object so a cast to the correct type is required. Deserialization is the process of getting the actual object back from the serialized form of an object. Deserialization is also known as one of the way of creating an object in java see this How many ways to create an object.

OK, Lets De-serialized the object, below is the method to Deserialized the object.

public static void deSerializedObject(String fileName) {
  FileInputStream fis;
  ObjectInputStream ois;
  try {
   fis = new FileInputStream(fileName);
   ois = new ObjectInputStream(fis);

   SerializableSecret secret = (SerializableSecret) ois.readObject();
   System.out.println(secret.getSecretString());

  } catch (IOException | ClassCastException | ClassNotFoundException e) {
   e.printStackTrace();
  }
 }
Out Put: My Pay-Check

Note: Desirialization will never call the constructor to create the object from the serialized form of the object. The Deserialization means not creating a new instance via constructor, It is just to getting back (restoring) the original object from the serialized version of the object. But don't worry you can customize the default protocol if you wish, we will discuss it later it this article.

Till now we have seen the basics of serialization and how to implement the serialization in java.
What if we don't want to serialized a particular field, We know that to serialize an Object we have to implement a Serializable interface. But if you don't want to serialized a particular field then mark all non-serializable fields as transient.
For example, System-level classes such as Thread, OutputStream and its subclasses, and Socket are not serializable, so mark them as transient.

The field which marks as transient can not be serialized, so the transient fields are assign as default values of the data type for example,
  • primitive types = 0
  • reference types = null
  • and for boolean =false

For example:

package com.javamakeuse.poc;
import java.io.Serializable;

public class SerializableObject implements Serializable {

 private String secretString;
 private transient boolean serialized;
 private transient int noOfObject;

 public String getSecretString() {
  return secretString;
 }

 public void setSecretString(String secretString) {
  this.secretString = secretString;
 }

 public boolean isSerialized() {
  return serialized;
 }

 public void setSerialized(boolean serialized) {
  this.serialized = serialized;
 }

 public int getNoOfObject() {
  return noOfObject;
 }

 public void setNoOfObject(int noOfObject) {
  this.noOfObject = noOfObject;
 }

}
If we serialized the SerializableObject class it will serialized only secretString field value other of two serialized and noOfObject fields are not serialized because we marked them as transient.

Customize the Default Protocol:
What if we have a requirement to call the constructor at the time of De-serialization on the serialized object, and what if you have a requirement to do something just before the serialization. We know that at the time of serialization writeObject(obj) method of java.io.ObjectOutputStream is called and at the time of De-serialization readObject() method of java.io.ObjectInputStream class is called.

Well, We can do the way we want for example, we can call constructor at the time of De-serialization. We can declare these two methods in our serializable class
  • private void writeObject(ObjectOutputStream out) throws IOException;
  • private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException;
The methods must be declare with private access specifiers so that no one can inherit these methods. Now whenever you call ObjectOutputStream.writeObject(obj) or ObjectInputStream.readObject(). The JVM will automatically call the private methods of your class and the object serialization works the same way as far any calling object is concerned.
For example,

package com.javamakeuse.poc;

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

public class SerializableSecret implements Serializable {

 private String secretString;
 private transient int noOfObject;

 public SerializableSecret() {
  System.out.println("Ohh!! constructor is called.");
 }

 public String getSecretString() {
  return secretString;
 }

 public void setSecretString(String secretString) {
  this.secretString = secretString;
 }

 public int getNoOfObject() {
  return noOfObject;
 }

 public void setNoOfObject(int noOfObject) {
  this.noOfObject = noOfObject;
 }

 private void writeObject(ObjectOutputStream out) throws IOException {
   out.defaultWriteObject();
 }

 private void readObject(ObjectInputStream in) throws IOException,
   ClassNotFoundException {
  
   in.defaultReadObject();
   // creating a new instance via getInstance() method.
   getInsntace();
 }

 private SerializableSecret getInsntace() {
  return new SerializableSecret();
 }
}
Now if you serialized/de-serialized the SerializableSecret class it will call the writeObject() and readObject() method of SerializableSecret class and work as per normal serialization protocol because we are not replacing the normal process, we are only adding to it a helper method to create an instance at the time of De-serialization via calling a constructor of the class. At the time of serialized/de-serialized first, the object is checked to ensure it implements Serializable and then it is checked to see whether either of those private methods are provided. If they are provided, the stream class is passed as the parameter, giving the code control over its usage.
Those private methods can be used for any customization you need to make to the serialization process.

Now what if you create a class whose super class is implementing a Serializable interface, but you don't want that new class to be serialized. You cannot un-implement an interface, so if your super class does implements the Serializable interface, your new class implement it too. How to stop the serialization of your new class?

To stop the automatic serialization of your new class you can again use the private method to just throw the NotSerializableException. Provide the below methods in your newly created class.

private void writeObject(ObjectOutputStream out) throws IOException {
  throw new NotSerializableException("ohh you can't");
 }

 private void readObject(ObjectInputStream in) throws IOException,
   ClassNotFoundException {
  throw new NotSerializableException("ohh you can't");
 }
Now whenever writeObject() or readObject() method is called it will throws the NotSerializableException.

Create Your Own Protocol:
java.io.Externalizable interface allow you to create your own protocol instead of implementing Serializable interface you can implement Externalizable interface unlike Serializable, Externalizable interface is not a marker interface. Externalizable interface has two methods to write/read.
  • public void writeExternal(ObjectOutput out) throws IOException;
  • public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException;
Override these two methods and provide your own protocol. Unlike the java.io.Serializable you will not get automatic serialization capability for the objects of your class. You have to provide the implementation logic of writeExternal() and readExternal() method.
Via Externarlizable you have full control over the serialization, If your class implements the Externarlizable interface, then you don't need to worry about how to call the writeExteranl() and readExternal() method. You just need to call writeObject() and readObject() method and the Externalizable methods will be called automatically.

For example:

package com.javamakeuse.poc;

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

public class Soccer implements Externalizable {

 private String manufacturer;
 private String model;

 public String getManufacturer() {
  return manufacturer;
 }

 public void setManufacturer(String manufacturer) {
  this.manufacturer = manufacturer;
 }

 public String getModel() {
  return model;
 }

 public void setModel(String model) {
  this.model = model;
 }

 @Override
 public void writeExternal(ObjectOutput out) throws IOException {
  out.writeObject(model);
  out.writeObject(manufacturer);

 }

 @Override
 public void readExternal(ObjectInput in) throws IOException,
   ClassNotFoundException {
  model = (String) in.readObject();
  manufacturer = (String) in.readObject();
 }

}
We have created a Serializable Soccer class via implementing an Externalizable interface and provides the implementation of writeExternal() and readExternal() methods. The implementation order of writeExternal() and readExternal() methods must be same to get the expected result at the time of de-serialzation. For example inside writeExternal() method we have written model object first as out.writeObject(model); and the same way inside readExternal() method we read model first, if we change the sequence in readExternal() method then we will get the unexpected result while calling getModel we may get the manufacturer values.

Note: While implementing Externalizalbe interface we must have to provide a no-arg constructor otherwise it will throws a java.io.InvalidClassException: com.javamakeuse.poc.Soccer; no valid constructor, at the time of de-serialization.

Here is SoccerMain.java class to serialized/deserialized the Soccer object.

package com.javamakeuse.poc;

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

public class SoccerMain {

 // Serialized the objecct
 private static void serialized(Object object) throws IOException {
  FileOutputStream fos = new FileOutputStream("soccer.ser");
  ObjectOutputStream out = new ObjectOutputStream(fos);
  out.writeObject(object);
  out.close();

 }

 // De-serialized the object
 private static void deSerialzied(String file) throws IOException,
   ClassNotFoundException {
  FileInputStream fis = new FileInputStream(file);
  ObjectInputStream in = new ObjectInputStream(fis);
  Soccer soccer = (Soccer) in.readObject();
  in.close();
  // printing the model of soccer
  System.out.println("Soccer Model is => "+soccer.getModel());

 }

 public static void main(String[] args) {
  Soccer soccer = new Soccer();
  soccer.setManufacturer("adidas");
  soccer.setModel("FiFA2014");
  try {
   // calling serialized() method.
   serialized(soccer);
   // calling deSerialized() method.
   deSerialzied("soccer.ser");

  } catch (IOException | ClassNotFoundException e) {
   e.printStackTrace();
  }
 }
}

OUT PUT: Soccer Model is => FiFA2014

Using Externalizable interface you reduce the performance issue(we will see how, later on this tutorial) and take additional overhead to maintain the logic of writeExternal() and readExternal() methods yourself. If you add/update/remove a field from your class you have to change each time the logic of writeExternal() and readExternal() method to reflect the changes.

That is not enough for Externalizable interface, using Externalizable interface you can create your own protocol so mean, If you know how to write and read some other format of document such PDF then you can provide the PDF-specific protocol in the writeExternal and readExternal methods also.

Version Control:
If you create a Serializable class and serialized the object stream. Once you serialized the object stream, you have add/change/remove a field from the Serializable class and going to restore the original object (de-serialized) from the object stream, what happen?

Well Restoring object will throws a java.io.InvalidClassException
For example,

package com.javamakeuse.poc;

import java.io.Serializable;

public class Product implements Serializable {
 private String productName;

 public String getProductName() {
  return productName;
 }

 public void setProductName(String productName) {
  this.productName = productName;
 }

}
Now serialized a Product object in SerialVersionTest.java class

package com.javamakeuse.poc;

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

public class SerialVersionTest {

 private static String file = "product.ser";

 private static void serialized(Object obj) throws IOException {
  FileOutputStream fos = new FileOutputStream(file);
  ObjectOutputStream out = new ObjectOutputStream(fos);
  out.writeObject(obj);
  out.close();
 }

 public static void main(String[] args) {
  Product product = new Product();
  product.setProductName("pendrive");
  try {
   serialized(product);
  } catch (IOException e) {
   e.printStackTrace();
  }
 }
}

Run SerialVersionTest.java class, It will serialized the product object and create a product.ser file. Now add a productPrice field in Product.java class. For example

package com.javamakeuse.poc;

import java.io.Serializable;

public class Product implements Serializable {
 private String productName;

 // added new field
 private double productPrice;

 public String getProductName() {
  return productName;
 }

 public void setProductName(String productName) {
  this.productName = productName;
 }

 public double getProductPrice() {
  return productPrice;
 }

 public void setProductPrice(double productPrice) {
  this.productPrice = productPrice;
 }

}

Now, Restore(De-Serialized) the object from the serialized object. Here is a SerialVersionTest.java class to De-Serialized the stream.

package com.javamakeuse.poc;

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

public class SerialVersionTest {

 private static String file = "product.ser";
 
 private static void deSerialized() throws IOException, ClassNotFoundException{
  FileInputStream fis= new FileInputStream(file);
  ObjectInputStream in = new ObjectInputStream(fis);
  
  Product product = (Product)in.readObject();
  in.close();
  //printing product name
  System.out.println(product.getProductName());
  
 }
 public static void main(String[] args) {
  try {
   deSerialized();
  } catch (IOException | ClassNotFoundException e) {
   e.printStackTrace();
  }
 }
}
OUT PUT:
java.io.InvalidClassException: com.javamakeuse.poc.Product; local class incompatible: stream classdesc serialVersionUID = 2360281018254192478, local class serialVersionUID = 7079879676188092747
at java.io.ObjectStreamClass.initNonProxy(ObjectStreamClass.java:604)
at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1601)
at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1514)
at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1750)
at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1347)
at java.io.ObjectInputStream.readObject(ObjectInputStream.java:369)
at com.javamakeuse.poc.SerialVersionTest.deSerialized(SerialVersionTest.java:15)
at com.javamakeuse.poc.SerialVersionTest.main(SerialVersionTest.java:22)

The InvalidClassException occurred because all the serializable classes are automatically given a unique identifier(serialVersionUID). If the identifier of the class does not equal to serialized object, It will throws InvalidClassException. The unique identifier of the class is maintained by serialVersionUID field. The default value of serialVersionUID is hash code of the object. You can control the versioning by providing the serialVersionUID field manually and make sure that the value of serialVersionUID always the same.

For example,

package com.javamakeuse.poc;

import java.io.Serializable;

public class Product implements Serializable {

 private static final long serialVersionUID = 123L;

 private String productName;

 public String getProductName() {
  return productName;
 }

 public void setProductName(String productName) {
  this.productName = productName;
 }

}
Now serialized a Product object, Once serialization is done add a productPrice in the Product class.

Updated Product.java:

package com.javamakeuse.poc;

import java.io.Serializable;

public class Product implements Serializable {

 private static final long serialVersionUID = 123L;

 private String productName;
 private double productPrice;

 public String getProductName() {
  return productName;
 }

 public void setProductName(String productName) {
  this.productName = productName;
 }

 public double getProductPrice() {
  return productPrice;
 }

 public void setProductPrice(double productPrice) {
  this.productPrice = productPrice;
 }

}
We have added a new field in Product.java class after serialization but we have not changed the serialVersionUID, so now restoring(de-serialized) of object will not throws any exception and work perfect.
Test the program:

package com.javamakeuse.poc;

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

public class SerialVersionTest {

 private static String file = "product.ser";

 private static void deSerialized() throws IOException,
   ClassNotFoundException {
  FileInputStream fis = new FileInputStream(file);
  ObjectInputStream in = new ObjectInputStream(fis);
  
  Product product = (Product) in.readObject();
  in.close();
  
  System.out.println(product.getProductName());

 }

 public static void main(String[] args) {
  try {
   deSerialized();
  } catch (IOException | ClassNotFoundException e) {
   e.printStackTrace();
  }
 }
}
Run the program you will get the expected output.
OUT PUT: pendrive

When to use serialization?

  • To transfer object over the network.
  • To write object inside hard disk.

Things to keep in mind while implementing serialization

  • Serializable class, Must implements either the Serializable interface or it's sub interface called Externalizable.
  • Mark all non-serializable fields as transient.
  • System-level classes such as Thread, OutputStream and its subclasses, and Socket are not serializable.
  • Provides the serialVersionUID to control the versioning.

For performance point of view always use Externalizable interface.

References:
Reference 1
Reference 2

That's it.


Sponsored Links

0 comments:

Post a Comment