Thursday, September 06, 2012

Java serialization mechanisms

In Java we can create reusable objects in memory, but they have a limited life, as long the Java virtual machine remains running.

Java serialization is a mechanism where an object's state can be saved as a sequence of bytes that have the object's data and information about the objects's type and the types of data stored in the object and can later be fully restored to regenerate the original object. You can use object serialization on any object that implements the java.io.Serializable or the java.io.Externalizable interface.

The Serializable interface is just a marker interface (it has no methods or fields). Transient keyword is a modifier applied to fields (like time elapsed or logger) that are not part of the persistent state of the object and thus never saved during serialization.


Example:
import java.io.Serializable;

public class PersistentObject implements Serializable {
  String name = "DefaultName";
}


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


public class MainClass {

  public static void main(String[] args) {
    String filename = "persistentObject.ser";
    PersistentObject persistentObject = new PersistentObject();
    FileOutputStream fos = null;
    ObjectOutputStream out = null;
    try {
      fos = new FileOutputStream(filename);
      out = new ObjectOutputStream(fos);
      out.writeObject(persistentObject);
    }
    catch (IOException ex) {
      System.out.println("couldn't serialize");
    }
    finally {
      if (out != null) {
        try {
          out.close();
        }
        catch (IOException e) {
          System.out.println("couldn't close the object output stream");
        }
      }
    }
  }
}

and the content of persistenObject.ser file viewed with a HEX editor is

where you can see the type of my field as java.lang.String and the value and now if I make the field name transient it won't be saved in my file.
 transient String name = "DefaultName";

This is how we read a serializable object from a file:


import java.io.*;


public class MainClass {

  public static void main(String[] args) {
    String filename = "persistentObject.ser";
    PersistentObject persistentObject = null;
    FileInputStream fis = null;
    ObjectInputStream in = null;
    try {
      fis = new FileInputStream(filename);
      in = new ObjectInputStream(fis);
      persistentObject = (PersistentObject) in.readObject();
    }
    catch (IOException ex) {
      System.out.println("couldn't read the object");
    }
    catch (ClassNotFoundException ex) {
      System.out.println("couldn't the class to cast the object from the file: " + filename);
    }
    finally {
      if (in != null) {
        try {
          in.close();
        }
        catch (IOException e) {
          System.out.println("couldn't close the input stream);
        }
      }
    }
    System.out.println("value from object is: " + persistentObject.name);
  }
}

and after running this application we'll see in console: "value from object is: DefaultName".

In case there is a graph and has an object that does not support the Serializable interface a NotSerializableException will be thrown and will identify the class of the non-serializable object.

Example:
Having an animal class which doesn't implement the java.io.Serializable interface

public class Animal {
    private String name;
    public String getName() {
        return name;
    }
    public Animal(String name) {
        this.name = name;
    }
}

and the same persistence object that includes an instance of animal

import java.io.Serializable;
public class PersistentObject implements Serializable {
    String name = "DefaultName";
    Animal testAnimal = new Animal("Test");
}

If we try to write an object of type PersistentObject again
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;


public class MainClass {

  public static void main(String[] args) {
    String filename = "persistentObject.ser";
    PersistentObject persistentObject = new PersistentObject();
    FileOutputStream fos = null;
    ObjectOutputStream out = null;
    try {
      fos = new FileOutputStream(filename);
      out = new ObjectOutputStream(fos);
      out.writeObject(persistentObject);
    }
    catch (IOException ex) {
      System.out.println("couldn't serialize");
    }
    finally {
      if (out != null) {
        try {
          out.close();
        }
        catch (IOException e) {
          System.out.println("couldn't close the object output stream");
        }
      }
    }
  }
}

we'll get the following stacktrace

java.io.NotSerializableException: ro.blogspot.valentinjava.Animal
at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1164)
at java.io.ObjectOutputStream.defaultWriteFields(ObjectOutputStream.java:1518)
at java.io.ObjectOutputStream.writeSerialData(ObjectOutputStream.java:1483)
at java.io.ObjectOutputStream.writeOrdinaryObject(ObjectOutputStream.java:1400)
at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1158)
at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:330)
at ro.blogspot.valentinjava.MainClass.main(MainClass.java:46)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)...

and the written object can't be read.

The serialization mechanism can be customized with the help of  the java.io.Externalizable  interface white has two methods writeExternal(ObjectOutput out) method to save the state of the object and readExternal(ObjectInput in) method to read the data written by the writeExternal method from the stream and restore the state of the object.

Each class is good to have a serial version number, that is used to identify the original class version which it can be used of writing streams and from which it can be read. For example, a class must have:

 private static final long serialVersionUID = -8997273866406805568L;
and this number is a 64-bit hash of the class name, interface class names, methods, and fields. As a remark for dynamic proxy classes and enum types the serial version number is always 0L.

If you want to deserialize a class which was serialized with a different serialVersionUID you'll get an Exception in thread "main" java.io.InvalidClassException.

No comments: