Friday, July 15, 2016

Tutorial on Serialization in Java - Part3

How to customize Serialization:

Anyhow, Java handles serialization process then what’s the need of customizing it. Let’s look at a small scenario.

Let’s consider our TestObject class has a transient field so that it cannot get serialized and imagine we do not have the source code for this class and we have a requirement that we need to get this field serialized which has defined as transient in the source code like below.


TestObject.java

package Serialization;

import java.io.Serializable;

public class TestObject implements Serializable {
       transient int number;
       String name;
       TestObject(int i, String s) {
              number = i;
              name = s;
       }
}

Since its transient, the default serialization provided by Java will not serialize the transient field then how do we handle this situation? This is where custom serialization comes into picture.

SerializationTest.java

package Serialization;

public class SerializationTest {
       public static void main(String[] args) {
              TestObject tObj1 = new TestObject (10, "TestSerialization");
              SerializeUtility utility = new SerializeUtility();
             
              // serialization
              utility.serialize(tObj1);
              // deserialization
              TestObject tObj2 = (TestObject)utility.deSerialize();
             
              System.out.println("Number: " + tObj2.number);
              System.out.println("Name: " + tObj2.name);
       }
}

If you compile and run ‘SerializationTest.java’, you will get the following output:

Number: 0
Name: TestSerialization

So, To handle this situation (to get the transient field ‘number’ serialized), let’s create a sub class DerivedTestObject  from super class TestObject and will provide customized serialization by defining writeObject() and readObject() methods with the below prototypes.

  • private void writeObject(ObjectOutputStream out) throws IOException;
  • private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException;
The prototypes must be similar to above. These methods must be private and return types as void.  When we call writeObject(obj) from ObjectOutputStream instance then JVM will look for these methods whether these have been defined in the obj’s class. If they have been defined,  JVM will start executing these instead calling its default serialization.

Now, let’s define writeObject(ObjectOutputStream out) and readObject(ObjectInputStream in) methods in DerivedTestObject like below:

DerivedTestObject.java

package Serialization;

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

public class DerivedTestObject extends TestObject {

       DerivedTestObject(int i, String s) {
              super(i, s);
       }
      
       private void writeObject(ObjectOutputStream oos) throws IOException {
              oos.writeObject(name);
              oos.writeInt(number);
       }
      
       private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
              this.name = (String) ois.readObject();
              this.number = ois.readInt();
       }
}

Please note that the order we read from ObjectInputStream in readObject() should be same as the order we write into ObjectOutputStream in writeObject().
If we try to serialize DerivedTestObject instead of TestObject, then you can observe that the transient field ‘number’ gets serialized now.

SerializationTest.java

package Serialization;

public class SerializationTest {
       public static void main(String[] args) {
              TestObject tObj1 = new DerivedTestObject(10, "TestSerialization");
              SerializeUtility utility = new SerializeUtility();
             
              // serialization
              utility.serialize(tObj1);
              // deserialization
              TestObject tObj2 = (DerivedTestObject)utility.deSerialize();
             
              System.out.println("Number: " + tObj2.number);
              System.out.println("Name: " + tObj2.name);
       }
}

If you compile and run ‘SerializationTest.java’, you will get the following output:

Number: 10
Name: TestSerialization

Let’s look at other scenario now. Let’s consider our DerivedTestObject class contains a field which is a reference of OtherObject which is not serializable (means the class OtherObject does not implement Serializable interface). So let’s create a class OtherObject and add a reference of it to DerivedTestObject class as a field like below.

OtherObject.java

package Serialization;

public class OtherObject {
      
       int test = 10;
      
       @Override
       public String toString() {
              return "test: " + test;
       }
}

DerivedTestObject.java

package Serialization;

public class DerivedTestObject extends TestObject {
      
       OtherObject otherObject;

       DerivedTestObject(int i, String s, OtherObject oo) {
              super(i, s);
              otherObject = oo;
       }
      
}


SerializationTest.java

package Serialization;

public class SerializationTest {
       public static void main(String[] args) {
              DerivedTestObject tObj1 = new DerivedTestObject (10, "TestSerialization", new OtherObject());
              SerializeUtility utility = new SerializeUtility();
             
              // serialization
              utility.serialize(tObj1);
              // deserialization
              DerivedTestObject tObj2 = (DerivedTestObject)utility.deSerialize();
             
              System.out.println("Number: " + tObj2.number);
              System.out.println("Name: " + tObj2.name);
              System.out.println("OtherObject: " + tObj2.otherObject);
       }
}

If you compile and run ‘SerializationTest.java’, you will get the following exception as OtherObject did not implement Serializable interface.

java.io.NotSerializableException: Serialization.OtherObject

To avoid this exception, as you guys know that we simply need to make OtherObejct class implements Serializable interface.

OtherObject.java

package Serialization;
import java.io.Serializable;

public class OtherObject implements Serializable {
      
       int test = 10;
      
       @Override
       public String toString() {
              return "test: " + test;
       }
}

If you compile and run ‘SerializationTest.java’, you will get the following output:

Number: 0
Name: TestSerialization
OtherObject: test: 10


 Let’s make it bit complex, suppose that we don’t have access to the source code of OtherObject class to make it implements Serializable interface, then how do we handle this situation?

We can handle this situation (avoid serialization of OtherObject reference ‘oo’) with customized serialization by defining writeObject() and readObject() methods with the below prototypes like we did in the earlier scenario.

  • private void writeObject(ObjectOutputStream out) throws IOException;
  • private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException;

OtherObject.java

package Serialization;

public class OtherObject {
      
       int test = 10;
      
       @Override
       public String toString() {
              return "test: " + test;
       }
}


DerivedTestObject.java

package Serialization;

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

public class DerivedTestObject extends TestObject {
      
       OtherObject otherObject;

       DerivedTestObject(int i, String s, OtherObject oo) {
              super(i, s);
              otherObject = oo;
       }
      
       private void writeObject(ObjectOutputStream oos) throws IOException {
              oos.writeObject(name);
              oos.writeInt(number);
       }
      
       private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
              this.name = (String) ois.readObject();
              this.number = ois.readInt();
       }      
}

We customized the serialization process by saving only name and number fields and ignoring OtherObject reference field.

If you compile and run ‘SerializationTest.java’, you will get the following output:

Number: 10
Name: TestSerialization
OtherObject: null


You can observe that the OtherObject reference field stored with its default value while deserializing as we escaped it from being serialized with our customized methods: writeObject() &  readObject().

You can still have the default serialization in the customized writeObject and readObject methods with the help of default methods like below:

private void writeObject(ObjectOutputStream oos) throws IOException {
              oos.defaultWriteObject();
              /* Custom Serialization */
       }
      
private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
              ois.defaultReadObject();
              /* Custom Serialization */
       }

1 comment: