Tuesday 29 November 2016

Implement Singleton with Enum

In my previous post, I explained how to implement a singleton pattern. This is continuation to my previous post, so I recommend you to go through the post, before reading this.


Singleton pattern restricts the instantiation of a class to one object. That is you can't create more than one object to this class.

Connection.java
package com.sample.singleton;

public class Connection {
 private static final Connection INSTANCE = new Connection();
 private int connId = 0;

 public static Connection getInstance() {
  return INSTANCE;
 }

 private Connection() {
  connId++;
  System.out.println("Connection object is created");
 }

 @Override
 public String toString() {
  StringBuilder builder = new StringBuilder();
  builder.append("Connection [connId=").append(connId).append("]");
  return builder.toString();
 }

}

ConnectionTest.java
package com.sample.singleton;

public class ConnectionTest {
 public static void main(String args[]) {
  Connection conn1 = Connection.getInstance();
  Connection conn2 = Connection.getInstance();
  Connection conn3 = Connection.getInstance();

  System.out.println("conn1 = " + conn1);
  System.out.println("conn2 = " + conn2);
  System.out.println("conn3 = " + conn3);

  System.out.println("conn1 == conn2 : " + (conn1 == conn2));
  System.out.println("conn1 == conn3 : " + (conn1 == conn3));
  System.out.println("conn2 == conn3 : " + (conn2 == conn3));
 }
}

Connection class works absolutely fine until Connection class don't implement Serializable interface. If Connection class implements Serializable interface, whenever application deserialize the object, it return new instance, which violates singleton property. Let's see it by an example.

Connection.java
package com.sample.singleton;

import java.io.Serializable;

public class Connection implements Serializable {
 private static final long serialVersionUID = 1234L;
 private static final Connection INSTANCE = new Connection();
 private int connId = 0;

 public static Connection getInstance() {
  return INSTANCE;
 }

 private Connection() {
  connId++;
  System.out.println("Connection object is created");
 }

 @Override
 public String toString() {
  StringBuilder builder = new StringBuilder();
  builder.append("Connection [connId=").append(connId).append("]");
  return builder.toString();
 }

}

ConnectionTest.java
package com.sample.singleton;

import java.io.*;

public class ConnectionTest {
 public static void serializeObject(Connection conn, String fileName) {
  try (FileOutputStream fos = new FileOutputStream(fileName);
    ObjectOutputStream out = new ObjectOutputStream(fos)) {
   out.writeObject(conn);
  } catch (IOException e) {
   System.out.println("Error while serializing Connection object " + e.getMessage());
  }

 }

 public static Connection deserializeObject(String fileName) {

  try (FileInputStream fis = new FileInputStream("ser.out"); ObjectInputStream in = new ObjectInputStream(fis)) {
   Connection conn = (Connection) in.readObject();
   return conn;
  } catch (Exception e) {
   System.out.println("Error while Deserializing Connection object " + e.getMessage());
  }
  return null;

 }

 public static void main(String args[]) {
  Connection conn1 = Connection.getInstance();
  String fileName = "ser.out";

  serializeObject(conn1, fileName);

  Connection conn2 = deserializeObject(fileName);
  Connection conn3 = deserializeObject(fileName);

  System.out.println("conn1 = " + conn1);
  System.out.println("conn2 = " + conn2);
  System.out.println("conn3 = " + conn3);

  System.out.println("conn1 == conn2 : " + (conn1 == conn2));
  System.out.println("conn1 == conn3 : " + (conn1 == conn3));
  System.out.println("conn2 == conn3 : " + (conn2 == conn3));
 }
}


Output
Connection object is created
conn1 = Connection [connId=1]
conn2 = Connection [connId=1]
conn3 = Connection [connId=1]
conn1 == conn2 : false
conn1 == conn3 : false
conn2 == conn3 : false

As you see the output, the objects conn1, conn2, conn3 are not equal. Every time application deserialize the object, it is returning different object.

How to resolve the issue?
To make singleton pattern works properly, Singleton class must implement the method readResolve. By using readResolve method, you can control what object should be returned on deserialization. Update Connection class like below.

Connection.java
package com.sample.singleton;

import java.io.Serializable;

public class Connection implements Serializable {
 private static final long serialVersionUID = 1234L;
 private static final Connection INSTANCE = new Connection();
 private int connId = 0;

 public static Connection getInstance() {
  return INSTANCE;
 }

 private Connection() {
  connId++;
  System.out.println("Connection object is created");
 }

 @Override
 public String toString() {
  StringBuilder builder = new StringBuilder();
  builder.append("Connection [connId=").append(connId).append("]");
  return builder.toString();
 }

 private Object readResolve() {
  return INSTANCE;
 }

}

Re run ConnectionTest.java, you can able to see following output.
Connection object is created
conn1 = Connection [connId=1]
conn2 = Connection [connId=1]
conn3 = Connection [connId=1]
conn1 == conn2 : true
conn1 == conn3 : true
conn2 == conn3 : true

Better way to provide singleton behavior to Connection class using Enum
From java1.5 onwards, by defining enum type with one element we can create singleton class very easily.

Update Connection class like below.


Connection.java
package com.sample.singleton;

import java.io.Serializable;

public enum Connection implements Serializable {
 INSTANCE;

 private int connId = 0;

 public static Connection getInstance() {
  return INSTANCE;
 }

 private Connection() {
  connId++;
  System.out.println("Connection object is created");
 }

 @Override
 public String toString() {
  StringBuilder builder = new StringBuilder();
  builder.append("Connection [connId=").append(connId).append("]");
  return builder.toString();
 }

}

Re run ConnectionTest.java file, you can able to see following output.
Connection object is created
conn1 = Connection [connId=1]
conn2 = Connection [connId=1]
conn3 = Connection [connId=1]
conn1 == conn2 : true
conn1 == conn3 : true
conn2 == conn3 : true

One advantage of this approach is, you no need to take care of serialization & deserialization issue, java handles it internally. I always prefer to implement singleton using enum.








No comments:

Post a Comment