Singleton Design pattern with Serialization

In this article, we will discuss singleton design pattern with respect to serialization in detail

Let me tell you scenario I have faced during one of the Java interview for leading investment banker in the market, few years back

Q) What are the things that need to be taken care for making a class as singleton ?

  • 1st thing make constructor as private such that no one outside the class can create an instance
  • 2nd provide public method to return same instance every time

Interviewer – That’s fine !!

Q) What if I serialize this singleton class and then de-serialize, won’t it create new instance ?

  • Exactly, we are going to discuss above scenario i.e.; how to stop creating a new instance during de-serialization

Before discussing that, we will make our-self clear few doubts that may arise (at least I had after giving interview)

Q) How to check that instance before serialization and instance restored after de-serialization are same or different ?

  • We can check using hashcode of both instances

Let us dive-deep and discuss all above things programmatically

 

1. Hash code of both instances are different

1.1 Customer POJO

  • A simple POJO class called Customer implementing java.io.Serializable interface to mark that this class got special ability (i.e.; it can be serialized and de-serialized)
  • Consists of private constructor, so that no one outside of class can construct a new object
  • A public method to return same instance every time which is eagerly initialized
  • Note: we can also initialize lazily, by null checking and initializing afterwards

Customer.java

package in.bench.resources.singleton.serialization;

import java.io.Serializable;

class Customer implements Serializable {

	// serialVersionUID
	private static final long serialVersionUID = 1L;

	// to always, return same instance
	private volatile static Customer CUSTOMER = new Customer();

	// private constructor
	private Customer() {
		// private constructor
	}

	// create static method to get same instance every time
	public static Customer getInstance(){
		return CUSTOMER;
	}

	// other methods and details of this class
}

1.2 Main class – Serialize and DeSerialize

  • Test class where both serialization and de-serialization happens in same class

CustomerSerializeDeSerializeDemo.java

package in.bench.resources.singleton.serialization;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;

public class CustomerSerializeDeSerializeDemo {

	public static void main(String[] args) {

		// create an customer object using 3-arg parametrized constructor
		Customer serializeCustomer = Customer.getInstance();

		// creating output stream variables
		FileOutputStream fos = null;
		ObjectOutputStream oos = null;

		// creating input stream variables
		FileInputStream fis = null;
		ObjectInputStream ois = null;

		// creating customer object reference 
		// to hold values after de-serialization 
		Customer deSerializeCustomer = null;

		try {
			// for writing or saving binary data
			fos = new FileOutputStream("Customer.ser");

			// converting java-object to binary-format 
			oos = new ObjectOutputStream(fos);

			// writing or saving customer object's value to stream
			oos.writeObject(serializeCustomer);
			oos.flush();
			oos.close();

			System.out.println("Serialization: "
					+ "Customer object saved to Customer.ser file\n");

			// reading binary data
			fis = new FileInputStream("Customer.ser");

			// converting binary-data to java-object
			ois = new ObjectInputStream(fis);

			// reading object's value and casting to Customer class
			deSerializeCustomer = (Customer) ois.readObject();
			ois.close();

			System.out.println("De-Serialization: Customer object "
					+ "de-serialized from Customer.ser file\n");
		} 
		catch (FileNotFoundException fnfex) {
			fnfex.printStackTrace();
		}
		catch (IOException ioex) {
			ioex.printStackTrace();
		}
		catch (ClassNotFoundException ccex) {
			ccex.printStackTrace();
		}

		// printing hash code of serialize customer object
		System.out.println("Hash code of the serialized "
				+ "Customer object is " + serializeCustomer.hashCode());

		// printing hash code of de-serialize customer object
		System.out.println("\nHash code of the de-serialized "
				+ "Customer object is " + deSerializeCustomer.hashCode());
	}
}

Output:

Serialization: Customer object saved to Customer.ser file

De-Serialization: Customer object de-serialized from Customer.ser file

Hash code of the serialized Customer object is 26253138

Hash code of the de-serialized Customer object is 33121026

Explanation:

  • From above output, it is clear that hashcode of both instances are different
  • Which means they are 2 different objects
  • Hence, making Customer class as singleton design pattern fails
  • Although, for every serialization hashcode remain same, until/unless if we change any class detail
  • But with every de-serialization, hashcode of Customer class might change

To suppress this behavior and make Customer class as singleton design pattern, we have provide/override one more method, which we are going to see in the next case

 

2. Hash codes of both instances are same by implementing readReolve(); method

2.1 Customer POJO

  • A simple POJO class called Customer implementing java.io.Serializable interface to mark that this class got special ability (i.e.; it can be serialized and de-serialized)
  • Consists of private constructor, so that no one outside of class can construct a new object
  • A public method to return same instance every time which is eagerly initialized
  • Note: we can also initialize lazily, by null checking and initializing afterwards
  • Finally, it contains readResolve(); method to suppress creation of new instance or say returns same singleton instance every time during de-serialization

Customer.java

package in.bench.resources.singleton.serialization;

import java.io.ObjectStreamException;
import java.io.Serializable;

class Customer implements Serializable {

	// serialVersionUID
	private static final long serialVersionUID = 1L;

	// to always, return same instance
	private volatile static Customer CUSTOMER = new Customer();

	// private constructor
	private Customer() {
		// private constructor
	}

	// create static method to get same instance every time
	public static Customer getInstance(){
		return CUSTOMER;
	}

	// readResolve method
	private Object readResolve() throws ObjectStreamException {
		return CUSTOMER;
	}

	// other methods and details of this class
}

2.2 Main class – to Serialize and DeSerialize

  • Test class where both serialization and de-serialization happens in same class

CustomerSerializeDeSerializeDemo.java

package in.bench.resources.singleton.serialization;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;

public class CustomerSerializeDeSerializeDemo {

	public static void main(String[] args) {

		// create an customer object 
		Customer serializeCustomer = Customer.getInstance();

		// creating output stream variables
		FileOutputStream fos = null;
		ObjectOutputStream oos = null;

		// creating input stream variables
		FileInputStream fis = null;
		ObjectInputStream ois = null;

		// creating customer object reference 
		// to hold values after de-serialization 
		Customer deSerializeCustomer = null;

		try {
			// for writing or saving binary data
			fos = new FileOutputStream("Customer.ser");

			// converting java-object to binary-format 
			oos = new ObjectOutputStream(fos);

			// writing or saving customer object's value to stream
			oos.writeObject(serializeCustomer);
			oos.flush();
			oos.close();

			System.out.println("Serialization: "
					+ "Customer object saved to Customer.ser file\n");

			// reading binary data
			fis = new FileInputStream("Customer.ser");

			// converting binary-data to java-object
			ois = new ObjectInputStream(fis);

			// reading object's value and casting to Customer class
			deSerializeCustomer = (Customer) ois.readObject();
			ois.close();

			System.out.println("De-Serialization: Customer object "
					+ "de-serialized from Customer.ser file\n");
		} 
		catch (FileNotFoundException fnfex) {
			fnfex.printStackTrace();
		}
		catch (IOException ioex) {
			ioex.printStackTrace();
		}
		catch (ClassNotFoundException ccex) {
			ccex.printStackTrace();
		}

		// printing hash code of serialize customer object
		System.out.println("Hash code of the serialized "
				+ "Customer object is " + serializeCustomer.hashCode());

		// printing hash code of de-serialize customer object
		System.out.println("\nHash code of the de-serialized "
				+ "Customer object is " + deSerializeCustomer.hashCode());
	}
}

Output:

Serialization: Customer object saved to Customer.ser file

De-Serialization: Customer object de-serialized from Customer.ser file

Hash code of the serialized Customer object is 26253138

Hash code of the de-serialized Customer object is 26253138

Explanation:

  • From above output, it is clear that hashcode of both instances are same
  • If we de-serialize again one more time, even then we will get same hashcode for both instances
  • Which proves , it is truly Singleton class

 

Read Also:

 

References:

 

Happy Coding !!
Happy Learning !!

How to stop Serialization in Java
Importance of SerialVersionUID in Serialization