Serialization

Overview

Why do we need it?

  • Normally the maximum life time of the object is from the program start till the program end. Serialization may help to keep object alive between the program executions.

  • Serialized object (as a byte stream) can be saved to the file and transferred by the network.

  • Serialization enables Java RMI (Remote Method Invocation) to be performed.

How to make a class Serializable

The class needs to implement a marker interface to become a Serializable.

public class User implements Serializable {}

Simple code example

The example demonstrates a serializable class, which is being serialized and deserialized through saving to the file inside of the test.

public class User implements Serializable {
	private static final long serialVersionUID = 1L;
	
	/**
	 * Static field belongs to the class, not to the instance.
	 * So it is not serialized.
	 */
	static String address = "theEarthPlanet";
	private int age;
	private String name;
	
	/**
	 * Transient fields are ignored during serialization.
	 */
	transient int height;

    // getters and setters of the fields
	}

Below is a test which performs serialization and deserialization of the user instance. Pay attention that transient class field height is ignored during the process of serialization.

public class SimpleSerialization {
	
	@Test
	public void whenSerializedAndDeserialized_objectIsTheSame() throws IOException, ClassNotFoundException {
	    // Arrange
		User user = new User();
		user.setAge(20);
		user.setName("theName");
		user.setHeight(180);
		
		String fileName = "theFile.txt";
		writeObjectToFile(user, fileName);
		
		// Act
		Object object = readObjectFromFile(fileName);
		User deserializedUser = (User) object;
		
		// Assert
		assertThat(deserializedUser.getAge()).isEqualTo(user.getAge());
		assertThat(deserializedUser.getName()).isEqualTo(user.getName());
		
		assertThat(deserializedUser.getHeight()).isNotEqualTo(user.getHeight());
	}
	
	
	private Object readObjectFromFile(String fileName) throws IOException, ClassNotFoundException {
		FileInputStream fileInputStream
				= new FileInputStream(fileName);
		ObjectInputStream objectInputStream
				= new ObjectInputStream(fileInputStream);
		Object object = objectInputStream.readObject();
		objectInputStream.close();
		return object;
	}
	
	
	private void writeObjectToFile(User user, String fileName) throws IOException {
		FileOutputStream fileOutputStream
				= new FileOutputStream(fileName);
		ObjectOutputStream objectOutputStream
				= new ObjectOutputStream(fileOutputStream);
		objectOutputStream.writeObject(user);
		objectOutputStream.flush();
		objectOutputStream.close();
	}
}

A source code can be found here.

Classes involved in serialization/deserialization

public final void writeObject(Object o) throws IOException;

can write primitive types or graph of objects to an OutputStream as a stream of bytes. And streams can then be read using ObjectInputStream by the method

public final Object readObject() throws IOException, ClassNotFoundException;

⚠️ TODO Create a sequence diagram of serialization/deserialization.

Caveats: Inheritance and Composition

Composition In case a class is composed of other classes, then each of these classes has to implement java.io.Serializable otherwise an exception NotSerializableException will be thrown during serialization process. On the diagram a class RootClass can be serialized. But during serialization of class Subclass an exception NotSerializableException is thrown, because one of the Subclass fields is a nonSerializable class (and it is important that value for this field is set, otherwise a field value is null and no exception is thrown during serialization). There is an example of composition for serialization. example

Inheritance correct case There is an edge case when subclass implements Serializable, but parent class not. Link to javadoc. Only the fields of Serializable objects are written out and restored. In current case it means that for Child objects, assuming both value and name were set to the object only name field values will be serialized/deserialized. And there will be no Runtime exceptions in this case. And fields of non-serializable Parent class will be initialised using its no-args constructor (public or protected), so this constructor should be accessible to the Child class. There is an example, which demonstrates the edge case for inheritance. example

Serial version UID

It is strongly recommended that all serializable classes explicitly declare the private field ( this field is not useful for inheritance):

private static final long serialVersionUID = 42L;

Why do we need this field?

  • the serialization runtime associates each serializable class with a version number, so this field is a version number

    • if this field is not deliberately specified in serializable class

      • => serialization runtime calculate default serialVersionUID for this class based on its attributes, associated access modifiers

      • => when you add/modify any field in class, which is already serialized, => class will not be able to recover, because serialVersionUID generated for new class and for old serialized are different => exception java.io.InvalidClassException is thrown

  • this field is used during deserialization to verify that saved and loaded objects have the same attributes and thus are compatible on serialization

There is an example where fragility of serialVersionUID is demonstrated. example

Custom serialization

⚠️ TODO

Last updated