Serialization

Serialization #

Serializing an object (resp. data structure) means converting it into a form that can be stored or transmitted, and such that the object (resp. data structure) can be later reconstructed (a.k.a. deserialized).

in Java #

Java provides a native mechanism to serialize an object (including information about the type of the object). The serialization format is not (meant to be) human-readable.

The process is JVM independent, meaning that an object can be serialized on one platform and deserialized on another.

Transient #

In Java, an instance attribute can be marked with the keyword transient. For instance:

public class MyClass {
  String serializedAttribute;
  transient int transientAttribute;
  ...
}

Transient attributes are excluded from the serialization process (meaning that a serialized object contains no value for its transient attribute).

Warning. When an object is deserialized (i.e. converted back to an object), default values are assigned to each of its transient attributes: null for a reference, 0 for an int, false for a boolean, etc.

Serializable #

In Java:

  • a value with primitive type (e.g. int) is serializable,
  • an array is serializable if its elements are serializable,
  • an object is serializable if:
    • its class implements the interface Serializable, and
    • each of its attributes is serializable or marked as transient.

Note. Implementing the interface Serializable does not require implementing any method.

Example. Instances of the following class are not serializable, because Country does not implement Serializable.

public class Country {

    String name;

    public Country(String name) {
        this.name = name;
    }
}

Instances of the following class are not serializable either, because the attribute country has type Country, which does not implement Serializable.

public class City implements Serializable {

    String name;
    int zipCode;
    Country country;

    public City(String name, int zipCode, Country country) {
        this.name = name;
        this.zipCode = zipCode;
        this.country = country;
    }
}

However, if we replace

    Country country;

with

    transient Country country;

then instances of the class City become serializable, because:

  • the attribute name has type String, which implements Serializable, and
  • the attribute zipCode has a primitive type, and
  • the attribute country is now declared as transient.

Note. Most native implementations of the Java interfaces Collection (ArrayList, LinkedList, HashSet, TreeSet, etc.) and Map (HashMap, TreeMap, etc.) also implement Serializable.

serialVersionUID #

If a class implements the interface Serializable, it is recommended to add a field: private static final long serialVersionUID (annotated with @Serial) and initialize it. For instance:

public class City implements Serializable {

    @Serial
    private static final long serialVersionUID = 0;
    ...
}

The value is irrelevant, but is meant to be updated if the (instance) attributes of the class are modified.

For an explanation, we refer to this page.

Serialization (and deserialization) methods #

The class ObjectOutputStream allows serializing an object, with the method:

void writeObject(Object x) throws IOException

Similarly, the class ObjectInputStream allows deserializing an object (i.e. loading it back into memory), with the method:

Object readObject() throws IOException, ClassNotFoundException

Note. The return type of readObject is Object, so the returned object needs to be cast to its appropriate data type.

Example. Let us continue with the example above (assuming that the attribute City.country is marked as transient).

A instance of City can be serialized to a file as follows:

String path = "path/to/file.ser";
Country italy = new Country("Italy");
City bologna = new City("Bologna", 40100, italy);

try (ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(path))) {
    out.writeObject(bologna);
} catch (IOException e) {
    throw new RuntimeException(e);
}

And deserialized as follows:

City deserializedBologna;
try (ObjectInputStream in = new ObjectInputStream(new FileInputStream(path))) {
  deserializedBologna = (City) in.readObject();
} catch (IOException | ClassNotFoundException e ) {
  throw new RuntimeException(e);
}

// Ouputs 'Deserialized city: { name: Bologna, zipCode: 40100, country: null }'
System.out.printf(
  "Deserialized city: { name: %s, zipCode: %d, country: %s }",
  deserializedBologna.name,
  deserializedBologna.zipCode,
  deserializedBologna.country
);

Observe that the attribute country after deserialization has value null (because it is marked as transient).