Serialization tutorial – Part III

You can go through the part II here, If you haven’t yet.

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:

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 */
       }

 

Note:

Either ObjectOutputStream’s defaultWriteObject or writeFields method must be called once (and only once) before writing any optional data that will be needed by the corresponding readObject method to restore the state of the object; even if no optional data is written, defaultWriteObject or writeFields must still be invoked once. If defaultWriteObject or writeFields is not invoked once prior to the writing of optional data (if any), then the behavior of instance deserialization is undefined in cases where the ObjectInputStream cannot resolve the class which defined the writeObject method in question.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s