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 */
}
Easy to grasp article.. nice work!!!
ReplyDelete