Dustin's Pages

Monday, February 10, 2014

Serializing Java Objects with Non-Serializable Attributes

There are multiple reasons one might want to use custom serialization instead of relying on Java's default serialization. One of the most common reasons is for performance improvements, but another reason for writing custom serialization is when the default serialization mechanism is unsupported. Specifically, as will be demonstrated in this post, custom serialization can be used to allow a larger object to be serialized even when attributes of that object are not themselves directly serializable.

The next code listing shows a simple class for serializing a given class to a file of the provided name and for deserializing an object from that same file. I will be using it in this post to demonstrate serialization.

SerializationDemonstrator.java

package dustin.examples.serialization;
import static java.lang.System.out;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
/**
* Simple serialization/deserialization demonstrator.
*
* @author Dustin
*/
public class SerializationDemonstrator
{
/**
* Serialize the provided object to the file of the provided name.
* @param objectToSerialize Object that is to be serialized to file; it is
* best that this object have an individually overridden toString()
* implementation as that is used by this method for writing our status.
* @param fileName Name of file to which object is to be serialized.
* @throws IllegalArgumentException Thrown if either provided parameter is null.
*/
public static <T> void serialize(final T objectToSerialize, final String fileName)
{
if (fileName == null)
{
throw new IllegalArgumentException(
"Name of file to which to serialize object to cannot be null.");
}
if (objectToSerialize == null)
{
throw new IllegalArgumentException("Object to be serialized cannot be null.");
}
try (FileOutputStream fos = new FileOutputStream(fileName);
ObjectOutputStream oos = new ObjectOutputStream(fos))
{
oos.writeObject(objectToSerialize);
out.println("Serialization of Object " + objectToSerialize + " completed.");
}
catch (IOException ioException)
{
ioException.printStackTrace();
}
}
/**
* Provides an object deserialized from the file indicated by the provided
* file name.
*
* @param <T> Type of object to be deserialized.
* @param fileToDeserialize Name of file from which object is to be deserialized.
* @param classBeingDeserialized Class definition of object to be deserialized
* from the file of the provided name/path; it is recommended that this
* class define its own toString() implementation as that will be used in
* this method's status output.
* @return Object deserialized from provided filename as an instance of the
* provided class; may be null if something goes wrong with deserialization.
* @throws IllegalArgumentException Thrown if either provided parameter is null.
*/
public static <T> T deserialize(final String fileToDeserialize, final Class<T> classBeingDeserialized)
{
if (fileToDeserialize == null)
{
throw new IllegalArgumentException("Cannot deserialize from a null filename.");
}
if (classBeingDeserialized == null)
{
throw new IllegalArgumentException("Type of class to be deserialized cannot be null.");
}
T objectOut = null;
try (FileInputStream fis = new FileInputStream(fileToDeserialize);
ObjectInputStream ois = new ObjectInputStream(fis))
{
objectOut = (T) ois.readObject();
out.println("Deserialization of Object " + objectOut + " is completed.");
}
catch (IOException | ClassNotFoundException exception)
{
exception.printStackTrace();
}
return objectOut;
}
}

The next code listing illustrates use of the SerializationDemonstrator class to serialize and deserialize a standard Java string (which is Serializable). A screen snapshot follows the code listing and shows the output (in NetBeans) of running that String through the serialize and deserialize methods of the SerializationDemonstrator class.

The next code listing demonstrates running SerializationDemonstrator on the serializable Person class with a non-serializable CityState. The code listing is followed by a screen snapshot of the output in NetBeans.

In this case, the CityState class is my own class and I could make it Serializable. However, supposing that this class was part of a third-party framework or library and I was not able to change the class itself, I can change Person to use custom serialization and deserialization and work with CityState properly. This is shown in the next code listing for the class SerializablePerson that is adapted from Person.

The above code listing shows that SerializablePerson has custom writeObject and readObject methods to support custom serialization/deserialization that handle its attribute of unserializable type CityState appropriately. A snippet of code for running this class through the SerializationDemonstrator and the successful output of doing so are shown next.

The approach depicted above will allow non-serializable types to be used as attributes of serializable classes without the need to make those fields transient. However, if the CityState instance shown earlier needs to be used in multiple serializable classes, it might be better to decorate the CityState class with a serializable decorator and then used that serialized decorator class in classes needing to be serialized. The next code listing shows SerializableCityState which decoratesCityState with a serialized version.

This serializable decorator can be used in the Person class directly and that enclosing Person can use default serialization because its fields are all serializable. This is shown in the next code listing for Person2 adapted from Person.

Custom serialization can be used to allow a class with attributes of nonserializable types to be serialized without making those attributes of nonserializable type transient. This is a useful technique when serializable classes need to use attributes of types that are not serializable and that cannot be changed.

3 comments:

fast-serialization (drop in replacement for jdk-ser) allows to register customserializers for foreign classes or allows to omit serializable check (forceSerializable(true)) in order to achieve this. Check it out :-)