Serialization in Java with example

What is Serialization?

Serialization is the process of encoding an object to byte stream and reverse of it is called deserialization.It is platform independent process which means you can serialize the object in one JVM and can transport the object over network and / or store it in filesystem and then deserialize it in other or on same JVM.

Class needs to implements marker interface Serializable in order to make their object to be eligible for Serialization.

fields with transient and/or static modifiers are not serialized by regular serialization process.

ObjectInputStream and ObjectOutputStream are high level stream classes which have methods to serialize and de-serialize Java objects.

ObjectOutputStream class has many write methods but the method that is usually used method is:

public final void writeObject(Object obj) throws IOException

ObjectInputStream class has many read methods but the method that is usually used method is:

public final Object readObject() throws IOException, ClassNotFoundException

Basic Serialization Example

Let's first define Dog pojo and then use ObjectInputStream and ObjectOutputStream for de-serialization and serialization.

public class Dog implements Serializable {
  private static final long serialVersionUID = 8661314562327474362L;

  private int height;
  private String name;

  // getters/ setters/ toString/ constructors
}

public class SimpleSerializationExample {
  public static void main(String[] args) {
    Dog dog = new Dog(50, "Titan"); // create dog object with height 50 and name Titan 
    System.out.println("Before Serialization");
    System.out.println(dog);
    try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("dog.ser"))) {
      oos.writeObject(dog);// serialize the dog object
    }
    catch (IOException ioEx) { /* Don't Swallow exception in real projects */ }
   
    dog = null; // let clear old dog object reference
    try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream("dog.ser"))) {
      dog = (Dog) ois.readObject();// deserialize dog object
    }
    catch (IOException | ClassCastException | ClassNotFoundException ex) { /* Don't Swallow exception in real projects */ }

    System.out.println("After Serialization");
    System.out.println(dog);
  }
}
Point to remember, if the class is not implementing Serializable interface then writeObject() method of ObjectOutputStream will throw run-time exception java.io.NotSerializableException

The above class was pretty straightforward with two primitive instance members. But, in real world could there be a case when

case 1: super class is not serializable but sub-class is.

case 2: You class have composed object of another class which doesn't implements Serializable interface.

case 1: super class is not serializable but sub-class is

There could be situation when you are extending some class which is not serializable but you want the sub-class be serializable. It is only possible if your super-class has

  • default no-args constructor.
  • supports ways to initialize safely the shared fields.
If the super-class doesn't implements Serializable interface then the inherited fields in the sub-class will be initialized to their default values and the Constructor will be called from first instance of non-serializable class till all the super class constructors are run. But in case of serializable class, then constructor will never run at the time of deserialization.

case 2: You have composed of object of another class which doesn't support serialization

The only solution is to mark that field reference as transient and do custom serialization for that.

writeObject and readObject to rescue for case 1 and case 2

Java serialization have special mechanism just for this. It has a set of private methods you can implement in your serializable class which will be invoked automatically during serilization and deserilization. The signature of these methods are :

private void writeObject(ObjectOutputStream os) {
 //Your custom code for serilization goes here
}

private void readObject(ObjectInputStream is) {
 //Your custom code for deserilization goes here
}

These methods let you step into the middle of serialization and deserialization.

Let's move to example.

public class Collar {
  private int size;
  // getters/ setters/ toString
}

public class Dog implements Serializable {

  private static final long serialVersionUID = 6870143058315212650L;

  private int height;
  private String name;
  private transient Collar myCollar;
  
  //getters/ setters/ toString
  private void writeObject(ObjectOutputStream os) throws IOException {
    try {
      os.defaultWriteObject();// lets first have default serialization happens
      os.writeInt(myCollar.getSize()); // save custom values
    } catch (IOException ex) { }
  }

  private void readObject(ObjectInputStream is) throws IOException, ClassNotFoundException {
    try {
      is.defaultReadObject();// lets first have default deserialization happens
      myCollar = new Collar(is.readInt()); // custom read values
    } catch (IOException | ClassNotFoundException ex) {
    }
  }
}

public class SimpleSerializationExample {
  public static void main(String[] args) {
    Dog dog = new Dog(1, "BuBu", new Collar(5)); // create dog object with height 50 and name Titan 
    System.out.println("Before Serialization");
    System.out.println(dog);
    try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("dog_collar.ser"))) {
      oos.writeObject(dog);// serialize the dog object
    }
    catch (IOException ioEx) {
      /* Don't Swallow exception in real projects */
    }

    dog = null; // let clear old dog object reference
    try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream("dog_collar.ser"))) {
      dog = (Dog) ois.readObject();// deserialize dog object
    }
    catch (IOException | ClassCastException | ClassNotFoundException e) {
      /* Don't Swallow exception in real projects */
    }
   
    System.out.println("After Serialization");
    System.out.println(dog.toString());
  }
}
Explanation
  • Line 15 and 22 declares writeObject and readObject methods in the Dog class.
  • Line 17 invoked defaultWriteObject() of ObjectOutputStream from writeObject(). We are telling JVM to perform the normal serialization. Line 18 did custom writing to the serialized object.
  • Line 24 invoked defaultReadObject() of ObjectInputStream from readObject() to handle normal serialization and then our custom deserialization starts at Line 25.
If we are customizing the serialization and deserialization using readObject() and writeObject() methods, then we need to read (readObject()) in order of writing(writeObject())  in the ObjectOutputStream because it saves in the sequence we write it. If we don't do so, we may end up in half-baked or wrongly deserialized object.

No comments :

Post a Comment