Chapters
Singletons are classes that have only one instance that may be accessed globally. To create a singleton class, we need three elements: static reference, private constructor and static factory method. Also, there are two types of singleton design pattern: Early Instantiation and Lazy Instantiation.
Early Instantiation is the creation of an instance at load time. This example demonstrates Early Instantiation.
Lazy Instantation is the creation of an instance when it's only needed. This example demonstrates Lazy Instantiation.
An enum class can be a singleton. An enum class is inherently constant, so, if we want a singleton that is constant then, consider using enum class as singleton. Creation of enum instance is thread safe. However, the methods are not.
Singleton is easy to implement and can be useful in some situations. Though, this design pattern has issues with other java concepts.
If a singleton class is loaded by two classloaders, each classloader will create an instance of the singleton class. This breaks the principle of singleton class.
If we deserialize a serialized singleton object, the singleton object that has been returned won't be the same as its previous form. Take a look at this example.
A singleton is unique per JVM. A system that involves multiple JVMs breaks the principle of singleton class. Systems that rely on distributed technologies may involve multiple JVMs.
A singleton object might be garbage-collected if it's not being referenced. If this happens, new instance will be created and the old one will be replaced. This may cause problems in some situations.
Singleton class
Singletons are classes that have only one instance that may be accessed globally. To create a singleton class, we need three elements: static reference, private constructor and static factory method. Also, there are two types of singleton design pattern: Early Instantiation and Lazy Instantiation.
Early Instantiation
Early Instantiation is the creation of an instance at load time. This example demonstrates Early Instantiation.
public class SampleClass{ public static void main(String[] args){ //get singleton instance Box box1 = Box.getInstance(); Box box2 = Box.getInstance(); System.out.println(box1 == box2); } } class Box{ //static reference //myBox is instantiated early private static Box myBox = new Box(); private static String name = "myBox"; //private constructor private Box(){} //static factory method. This //method produces and returns //the singleton instance public static Box getInstance(){ return myBox; } //getters public static String getName(){ return name; } }
Lazy Instantiation
Lazy Instantation is the creation of an instance when it's only needed. This example demonstrates Lazy Instantiation.
public class SampleClass{ public static void main(String[] args){ //get singleton instance Box box1 = Box.getInstance(); Box box2 = Box.getInstance(); System.out.println(box1 == box2); } } class Box{ //static reference private static Box myBox; private static String name = "myBox"; //private constructor private Box(){} //static factory method. This //method produces the singleton //instance //singleton instance is instantiated //when it's needed public static Box getInstance(){ if(myBox == null) myBox = new Box(); return myBox; } //getters public static String getName(){ return name; } }In the getInstance() method, we use null to check if there's already a singleton instance. Though, we can use boolean variable to check if a singleton instance exists.
class Box{ private static Box myBox; //boolean variable for checking private static boolean isExisting = false; private static String name = "myBox"; //private constructor private Box(){} public static Box getInstance(){ if(!isExisting){ myBox = new Box(); isExisting = true; } return myBox; } public static String getName(){ return name; } }
Enum Singleton
An enum class can be a singleton. An enum class is inherently constant, so, if we want a singleton that is constant then, consider using enum class as singleton. Creation of enum instance is thread safe. However, the methods are not.
public class SampleClass{ public static void main(String[] args){ System.out.println(Box.BOX.getName()); } } enum Box{ BOX("myBox"); private String name; private Box(String name){ this.name = name; } public Box getInstance(){ return BOX; } public String getName(){ return name; } }
Singleton Issues
Singleton is easy to implement and can be useful in some situations. Though, this design pattern has issues with other java concepts.
Classloaders
If a singleton class is loaded by two classloaders, each classloader will create an instance of the singleton class. This breaks the principle of singleton class.
Deserialization
If we deserialize a serialized singleton object, the singleton object that has been returned won't be the same as its previous form. Take a look at this example.
import java.io.File; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.Serializable; public class SampleClass{ public static void main(String[] args) throws IOException, ClassNotFoundException{ File file = new File("C:\\test\\testObj.obj"); try(ObjectOutputStream oos = new ObjectOutputStream( new FileOutputStream(file)); ObjectInputStream ois = new ObjectInputStream( new FileInputStream(file))){ Box input = Box.getInstance(); oos.writeObject(input); oos.flush(); Box output = (Box)ois.readObject(); System.out.println(input == output); } } } class Box implements Serializable{ private static Box myBox; private static boolean isExisting = false; private static String name = "myBox"; private Box(){} public static Box getInstance(){ if(!isExisting){ myBox = new Box(); isExisting = true; } return myBox; } public static String getName(){ return name; } } Result falseAs you can see, the Box object in input variable is different from the Box object in output variable. This issue, however, has a solution. We need to manually define the readResolve() method in a serializable class which is the Box class in this case. readResolve() method allows a class to replace/resolve the object read from the stream before it is returned to the caller.
import java.io.File; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.Serializable; public class SampleClass{ public static void main(String[] args) throws IOException, ClassNotFoundException{ File file = new File("C:\\test\\testObj.obj"); try(ObjectOutputStream oos = new ObjectOutputStream( new FileOutputStream(file)); ObjectInputStream ois = new ObjectInputStream( new FileInputStream(file))){ Box input = Box.getInstance(); oos.writeObject(input); oos.flush(); Box output = (Box)ois.readObject(); System.out.println(input == output); } } } class Box implements Serializable{ private static Box myBox; private static boolean isExisting = false; private static String name = "myBox"; //Constructor private Box(){} //manually define readResolve() method protected Object readResolve(){ return getInstance(); } public static Box getInstance(){ if(!isExisting){ myBox = new Box(); isExisting = true; } return myBox; } public static String getName(){ return name; } } Result trueRemember that readResolve() replaces the object from the stream. So, expect that the some values of the fields of the deserialized object may not be the same as their previous values.
import java.io.File; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.Serializable; public class SampleClass{ public static void main(String[] args) throws IOException, ClassNotFoundException{ File file = new File("C:\\test\\testObj.obj"); try(ObjectOutputStream oos = new ObjectOutputStream( new FileOutputStream(file)); ObjectInputStream ois = new ObjectInputStream( new FileInputStream(file))){ Box input = Box.getInstance(); System.out.println("Input name: " + input.getName()); System.out.println("Serializing input object..."); oos.writeObject(input); oos.flush(); System.out.println("Changing input name..."); input.setName("boxBox"); System.out.println("New input name: " + input.getName()); System.out.println("Deserializing..."); Box output = (Box)ois.readObject(); System.out.println("Output name: " + output.getName()); System.out.println(input == output); } } } class Box implements Serializable{ private static Box myBox; private static boolean isExisting = false; private static String name = "myBox"; //Constructor private Box(){} //manually define readResolve() method protected Object readResolve(){ return getInstance(); } public static Box getInstance(){ if(!isExisting){ myBox = new Box(); isExisting = true; } return myBox; } public static String getName(){ return name; } public static void setName(String newName){ name = newName; } } Result Input name: myBox Serializing input object... Changing input name... New input name: boxBox Deserializing... Output name: boxBox trueAs you can see from the result, the name of the Box object in input variable before serialization is different from the name of the Box object in output variable after serialization.
JVMs
A singleton is unique per JVM. A system that involves multiple JVMs breaks the principle of singleton class. Systems that rely on distributed technologies may involve multiple JVMs.
Garbage Collection
A singleton object might be garbage-collected if it's not being referenced. If this happens, new instance will be created and the old one will be replaced. This may cause problems in some situations.
No comments:
Post a Comment