Saturday, February 25, 2017

Singleton implementation two ways : Traditional & using single enum

Singleton can be implemented in two ways.
  1. Traditional way of static instance object and private constructor
  2. using Single object enum
Second way needs less coding.
I was comparing the the two approaches with respect to ways to override singleton behaviour and care to be taken by programmer
1) Cloning
In traditional way do not implement clonable interface or throw error in clone() method. In Enum way it is not needed as cloning is not possible as enum can not implement clonable method
2) Create object through reflection
In traditional way throw error in constructor if instance is already intialized. In Enum way it is not needed as we cannot reflectively create enum objects. It throws error " java.lang.IllegalArgumentException: Cannot reflectively create enum objects"
3) Serialization-deserialization
In traditional implement serializable interface or if implemented readResolve() method to return same instance.
Now in Enum are by default serializable. But we do not need to implement any method. Also serialization of Enum only saves Enum name. Other fields are not serialized.
So on deserialization it automatically returns reference to existing instance of enum. So latest info is available.  
Refer
http://stackoverflow.com/questions/42465564/readresolve-not-called-during-enum-desrialization/42466469#42466469
https://docs.oracle.com/javase/7/docs/platform/serialization/spec/serial-arch.html#6469

Below is the code
Traditional way singleton
package desgnpattern;

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


public class MySingleton implements  Cloneable , Serializable{
private static MySingleton instance = null;
private int age;
private String name;
private MySingleton(int age, String name){
if(instance!=null) throw new RuntimeException("Do not try to create class through reflection");
this.age = age;
this.name = name;
}
public int getAge() {
return age;
}
public String getName() {
return name;
}
public void setAge(int age) {
this.age = age;
}
public void setName(String name) {
this.name = name;
}
public static MySingleton getInstance(){
if(instance==null){
instance= new MySingleton(21, "narendra");
}
return instance;

}

@Override
public Object clone() throws CloneNotSupportedException {
throw new RuntimeException("Do not try to create class through reflection");
}


 //Hook for not breaking the singleton pattern while deserializing.
    private Object readResolve() throws ObjectStreamException {
    System.out.println("The constructed obejct has age="+age+" name="+name);
    System.out.println("But now returning same instance");
          return instance;
    }

    public void print(){
    System.out.println(getAge()+" ...."+getName());
    }
}


Test traditional singleton
public class SingletonCaller {

public static void main(String[] args) throws CloneNotSupportedException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException, FileNotFoundException, IOException, ClassNotFoundException {
// TODO Auto-generated method stub
//System.out.println(MySingleton.CM.getAge()+" ...."+MySingleton.CM.getName());

// Get singleton object
MySingleton ms1= MySingleton.getInstance();
System.out.println(ms1);
ms1.print();

//tryClone();
//tryReflection();
trySerialization();


}

public static void  tryClone() throws CloneNotSupportedException{
//Try cloning it is disabled
MySingleton clonedOnj = (MySingleton)MySingleton.getInstance().clone();
System.out.println(clonedOnj);
clonedOnj.print();
}

public static void  tryReflection() throws InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
// Call constructor using it is disabled
Constructor<?>[] constructArray= MySingleton.class.getDeclaredConstructors();
constructArray[0].setAccessible(true);
MySingleton reflectedobject = (MySingleton)constructArray[0].newInstance(31,"Kaushik");
System.out.println(reflectedobject);
reflectedobject.print();
}

public static void  trySerialization() throws FileNotFoundException, IOException, ClassNotFoundException{
String filePath = "d:\\kaushik\\kaushik26Feb.txt";
File f = new File(filePath);
f.createNewFile();

ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(filePath));
oos.writeObject(MySingleton.getInstance());
oos.close();
MySingleton.getInstance().setAge(999);
MySingleton.getInstance().setName("Before deserialize");
System.out.println("Printing values before deserialize");
MySingleton.getInstance().print();
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filePath));
MySingleton deserializedObj = (MySingleton)ois.readObject();

System.out.println(deserializedObj);
System.out.println(deserializedObj.getAge()+" ...."+deserializedObj.getName());

if(deserializedObj==MySingleton.getInstance()){
System.out.println("Same instance");
}else{
System.out.println("Different instance");
}
}
}


One Enum singleton
package desgnpattern;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectStreamException;
import java.io.Serializable;


/* New way of declaring singleton is create enum class and create one instance of enum */
public enum MySingletonEnum {
instance(41,"Devendra"),
instance2(42,"Devendra");

private MySingletonEnum(int age, String name){
this.age = age;
this.name = name;
}

private int age;
private String name;

public int getAge() {
return age;
}
public String getName() {
return name;
}
public void setAge(int age) {
this.age = age;
}
public void setName(String name) {
this.name = name;
}
public static MySingletonEnum getInstance(){
return instance;
}

public void print(){
System.out.println(getAge()+" ...."+getName());
    }

 //Hook for not breaking the singleton pattern while deserializing.
    private Object readResolve() throws ObjectStreamException {
    System.out.println("The constructed obejct has age="+age+" name="+name);
    System.out.println("But now returning same instance");
          return instance;
    }
    
    private void readObject(ObjectInputStream ois)
    throws ClassNotFoundException, IOException {
    System.out.println("In readObject");
       // default deserialization
       ois.defaultReadObject();
       MySingletonEnum obj = (MySingletonEnum)ois.readObject(); // Replace with real deserialization
       obj.print();
    }
}



Test one Enum Singleton

package desgnpattern;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;

public class EnumSingletonCaller {

public static void main(String[] args) throws CloneNotSupportedException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException, FileNotFoundException, IOException, ClassNotFoundException {

// Get singleton object
MySingletonEnum ms1= MySingletonEnum.getInstance();
System.out.println(ms1);
ms1.print();

// Cloning is not possible as enum can not implement clonable method

// Cannot reflectively create enum objects. So without extra code in constructor this case is automatically handled
//tryReflection();

trySerialization();


}


public static void  tryReflection() throws InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
// Call constructor using it is disabled
Constructor<?>[] constructArray= MySingletonEnum.class.getDeclaredConstructors();
constructArray[0].setAccessible(true);

// This method will throw errror
// java.lang.IllegalArgumentException: Cannot reflectively create enum objects
MySingletonEnum reflectedobject = (MySingletonEnum)constructArray[0].newInstance(31,"Kaushik");
System.out.println(reflectedobject);
reflectedobject.print();
}

public static void  trySerialization() throws FileNotFoundException, IOException, ClassNotFoundException{
String filePath = "d:\\kaushik\\kaushik26Feb.txt";
File f = new File(filePath);
f.createNewFile();

ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(filePath));
oos.writeObject(MySingletonEnum.getInstance());
oos.close();
MySingletonEnum.getInstance().setAge(999);
MySingletonEnum.getInstance().setName("Before deserialize");
System.out.println("Printing values before deserialize");
MySingletonEnum.getInstance().print();
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filePath));
MySingletonEnum deserializedObj = (MySingletonEnum)ois.readObject();

System.out.println("deserializedObj="+deserializedObj);
System.out.println(deserializedObj.getAge()+" ...."+deserializedObj.getName());

if(deserializedObj==MySingletonEnum.getInstance()){
System.out.println("Same instance");
}else{
System.out.println("Different instance");
}
}
}