Java custom serialization example
Introduction
By implementing the Serializable interface we are defining our class to be serializable by the JVM (we have already seen basic serialization before at Java Serializable example). This time we will cover the custom serialization: how it should be implemented and how it is called by the JVM.
Environment:
- Ubuntu 12.04
- JDK 1.7.0.09
Serializable - writeObject and readObject
When we implement the Serializable interface we are defining our class to be serializable. We have already seen that this is enough to support the default Java serialization mechanism. When we need to implement custom serialization we also have to define writeObject and/or readObject methods. Why did I mentioned define these methods and not override? Because we are actually not overriding anything: The JVM checks and calls these methods by the means of reflection.
Example one
Let's define a simple class to use in this example:
package com.byteslounge.serialization; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.Serializable; public class TestClass implements Serializable { private static final long serialVersionUID = -2518143671167959230L; private String propertyOne; private String propertyTwo; public TestClass(String propertyOne, String propertyTwo) { this.propertyOne = propertyOne; this.propertyTwo = propertyTwo; validate(); } private void writeObject(ObjectOutputStream o) throws IOException { o.writeObject(propertyOne); o.writeObject(propertyTwo); } private void readObject(ObjectInputStream o) throws IOException, ClassNotFoundException { propertyOne = (String) o.readObject(); propertyTwo = (String) o.readObject(); validate(); } private void validate(){ if(propertyOne == null || propertyOne.length() == 0 || propertyTwo == null || propertyTwo.length() == 0){ throw new IllegalArgumentException(); } } public String getPropertyOne() { return propertyOne; } public String getPropertyTwo() { return propertyTwo; } }
We have defined two properties: propertyOne and propertyTwo. We have also defined writeObject and readObject methods, and once again note that we are not overriding anything as these methods will be called by the JVM using reflection (we actually defined these methods as private). Finally we defined the validate method that will be used to validate if the object is correctly initialized in both construction and deserialization events (in our example we are just checking if both properties are not null or empty).
Testing Example one
Let's define a simple class to test our example:
package com.byteslounge.serialization; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; public class Main { public static void main(String[] args) throws Exception { TestClass testWrite = new TestClass("valueOne", "valueTwo"); FileOutputStream fos = new FileOutputStream("testfile"); ObjectOutputStream oos = new ObjectOutputStream(fos); oos.writeObject(testWrite); oos.flush(); oos.close(); TestClass testRead; FileInputStream fis = new FileInputStream("testfile"); ObjectInputStream ois = new ObjectInputStream(fis); testRead = (TestClass) ois.readObject(); ois.close(); System.out.println("--Serialized object--"); System.out.println("propertyOne: " + testWrite.getPropertyOne()); System.out.println("propertyTwo: " + testWrite.getPropertyTwo()); System.out.println(""); System.out.println("--Read object--"); System.out.println("propertyOne: " + testRead.getPropertyOne()); System.out.println("propertyTwo: " + testRead.getPropertyTwo()); } }
When we run this test the following output will be generated:
propertyOne: valueOne
propertyTwo: valueTwo
--Read object--
propertyOne: valueOne
propertyTwo: valueTwo
Example two
Let's define another class:
package com.byteslounge.serialization; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.Serializable; public class AnotherClass implements Serializable { private static final long serialVersionUID = -5606842333916087978L; private String propertyOne; private transient String propertyTwo; public AnotherClass(String propertyOne, String propertyTwo) { this.propertyOne = propertyOne; this.propertyTwo = propertyTwo; } private void writeObject(ObjectOutputStream o) throws IOException { o.defaultWriteObject(); o.writeObject(propertyTwo); } private void readObject(ObjectInputStream o) throws IOException, ClassNotFoundException { o.defaultReadObject(); propertyTwo = (String) o.readObject(); } public String getPropertyOne() { return propertyOne; } public String getPropertyTwo() { return propertyTwo; } }
This time we defined a class with a transient property. We have already seen that transient properties are not serialized by default (Java transient modifier example). We defined writeObject and readObject once again but this time we call the default serialization mechanism inside them. During serialization we call the default serialization - defaultWriteObject() - and then we call writeObject(propertyTwo). This way we write the transient property that would not be serialized by default. The same is valid for deserialization with defaultReadObject() and o.readObject().
Testing Example two
Let's define a simple class to test our example:
package com.byteslounge.serialization; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; public class Main { public static void main(String[] args) throws Exception { AnotherClass testWrite = new AnotherClass("valueOne", "valueTwo"); FileOutputStream fos = new FileOutputStream("testfile"); ObjectOutputStream oos = new ObjectOutputStream(fos); oos.writeObject(testWrite); oos.flush(); oos.close(); AnotherClass testRead; FileInputStream fis = new FileInputStream("testfile"); ObjectInputStream ois = new ObjectInputStream(fis); testRead = (AnotherClass)ois.readObject(); ois.close(); System.out.println("--Serialized object--"); System.out.println("propertyOne: " + testWrite.getPropertyOne()); System.out.println("propertyTwo: " + testWrite.getPropertyTwo()); System.out.println(""); System.out.println("--Read object--"); System.out.println("propertyOne: " + testRead.getPropertyOne()); System.out.println("propertyTwo: " + testRead.getPropertyTwo()); } }
When we run this test the following output will be generated:
propertyOne: valueOne
propertyTwo: valueTwo
--Read object--
propertyOne: valueOne
propertyTwo: valueTwo
Conclusion
We have seen two examples of custom serialization but they were just used as illustrative scenarios. The real usage of a custom serialization mechanism it's really bound to specific use cases, like using a custom format or infrastructure to store the serialized data or even refine the default serialization under some specific scenario.
The example source code is available for download at the end of this page.