先决条件:单例模式
在本文中,我们将看到哪些概念可以破坏类的单例属性以及如何避免它们。主要有 3 个概念可以破坏类的单例属性。让我们一一讨论它们。
- 反射:反射会破坏单例类的单例属性,如下例所示:
JAVA
// Java code to explain effect of Reflection
// on Singleton property
import java.lang.reflect.Constructor;
// Singleton class
class Singleton
{
// public instance initialized when loading the class
public static Singleton instance = new Singleton();
private Singleton()
{
// private constructor
}
}
public class GFG
{
public static void main(String[] args)
{
Singleton instance1 = Singleton.instance;
Singleton instance2 = null;
try
{
Constructor[] constructors =
Singleton.class.getDeclaredConstructors();
for (Constructor constructor : constructors)
{
// Below code will destroy the singleton pattern
constructor.setAccessible(true);
instance2 = (Singleton) constructor.newInstance();
break;
}
}
catch (Exception e)
{
e.printStackTrace();
}
System.out.println("instance1.hashCode():- "
+ instance1.hashCode());
System.out.println("instance2.hashCode():- "
+ instance2.hashCode());
}
}
JAVA
//Java program for Enum type singleton
public enum Singleton
{
INSTANCE;
}
JAVA
// Java code to explain effect of
// Serialization on singleton classes
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInput;
import java.io.ObjectInputStream;
import java.io.ObjectOutput;
import java.io.ObjectOutputStream;
import java.io.Serializable;
class Singleton implements Serializable
{
// public instance initialized when loading the class
public static Singleton instance = new Singleton();
private Singleton()
{
// private constructor
}
}
public class GFG
{
public static void main(String[] args)
{
try
{
Singleton instance1 = Singleton.instance;
ObjectOutput out
= new ObjectOutputStream(new FileOutputStream("file.text"));
out.writeObject(instance1);
out.close();
// deserialize from file to object
ObjectInput in
= new ObjectInputStream(new FileInputStream("file.text"));
Singleton instance2 = (Singleton) in.readObject();
in.close();
System.out.println("instance1 hashCode:- "
+ instance1.hashCode());
System.out.println("instance2 hashCode:- "
+ instance2.hashCode());
}
catch (Exception e)
{
e.printStackTrace();
}
}
}
JAVA
// Java code to remove the effect of
// Serialization on singleton classes
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInput;
import java.io.ObjectInputStream;
import java.io.ObjectOutput;
import java.io.ObjectOutputStream;
import java.io.Serializable;
class Singleton implements Serializable
{
// public instance initialized when loading the class
public static Singleton instance = new Singleton();
private Singleton()
{
// private constructor
}
// implement readResolve method
protected Object readResolve()
{
return instance;
}
}
public class GFG
{
public static void main(String[] args)
{
try
{
Singleton instance1 = Singleton.instance;
ObjectOutput out
= new ObjectOutputStream(new FileOutputStream("file.text"));
out.writeObject(instance1);
out.close();
// deserialize from file to object
ObjectInput in
= new ObjectInputStream(new FileInputStream("file.text"));
Singleton instance2 = (Singleton) in.readObject();
in.close();
System.out.println("instance1 hashCode:- "
+ instance1.hashCode());
System.out.println("instance2 hashCode:- "
+ instance2.hashCode());
}
catch (Exception e)
{
e.printStackTrace();
}
}
}
JAVA
// JAVA code to explain cloning
// issue with singleton
class SuperClass implements Cloneable
{
int i = 10;
@Override
protected Object clone() throws CloneNotSupportedException
{
return super.clone();
}
}
// Singleton class
class Singleton extends SuperClass
{
// public instance initialized when loading the class
public static Singleton instance = new Singleton();
private Singleton()
{
// private constructor
}
}
public class GFG
{
public static void main(String[] args) throws CloneNotSupportedException
{
Singleton instance1 = Singleton.instance;
Singleton instance2 = (Singleton) instance1.clone();
System.out.println("instance1 hashCode:- "
+ instance1.hashCode());
System.out.println("instance2 hashCode:- "
+ instance2.hashCode());
}
}
JAVA
// JAVA code to explain overcome
// cloning issue with singleton
class SuperClass implements Cloneable
{
int i = 10;
@Override
protected Object clone() throws CloneNotSupportedException
{
return super.clone();
}
}
// Singleton class
class Singleton extends SuperClass
{
// public instance initialized when loading the class
public static Singleton instance = new Singleton();
private Singleton()
{
// private constructor
}
@Override
protected Object clone() throws CloneNotSupportedException
{
throw new CloneNotSupportedException();
}
}
public class GFG
{
public static void main(String[] args) throws CloneNotSupportedException
{
Singleton instance1 = Singleton.instance;
Singleton instance2 = (Singleton) instance1.clone();
System.out.println("instance1 hashCode:- "
+ instance1.hashCode());
System.out.println("instance2 hashCode:- "
+ instance2.hashCode());
}
}
JAVA
// JAVA code to explain overcome
// cloning issue with singleton
class SuperClass implements Cloneable
{
int i = 10;
@Override
protected Object clone() throws CloneNotSupportedException
{
return super.clone();
}
}
// Singleton class
class Singleton extends SuperClass
{
// public instance initialized when loading the class
public static Singleton instance = new Singleton();
private Singleton()
{
// private constructor
}
@Override
protected Object clone() throws CloneNotSupportedException
{
return instance;
}
}
public class GFG
{
public static void main(String[] args) throws CloneNotSupportedException
{
Singleton instance1 = Singleton.instance;
Singleton instance2 = (Singleton) instance1.clone();
System.out.println("instance1 hashCode:- "
+ instance1.hashCode());
System.out.println("instance2 hashCode:- "
+ instance2.hashCode());
}
}
Output:-
instance1.hashCode():- 366712642
instance2.hashCode():- 1829164700
- 运行这个类后,你会看到 hashCode 不同,这意味着创建了同一个类的 2 个对象,并且单例模式已被销毁。
克服反射问题:为了克服反射引起的问题,使用枚举,因为Java 在内部确保枚举值仅实例化一次。由于Java枚举是全局可访问的,因此它们可用于单例。它唯一的缺点是它不灵活,即它不允许延迟初始化。
Java
//Java program for Enum type singleton
public enum Singleton
{
INSTANCE;
}
- 由于枚举没有任何构造函数,因此反射不可能使用它。枚举有它们的默认构造函数,我们不能自己调用它们。 JVM 在内部处理枚举构造函数的创建和调用。由于枚举不向程序提供其构造函数定义,因此我们也无法通过反射访问它们。因此,在枚举的情况下,反射不能破坏单例属性。
- 序列化:-序列化还会导致单例类的单例属性损坏。序列化用于转换字节流的对象并保存在文件中或通过网络发送。假设您序列化了一个单例类的对象。然后,如果您反序列化该对象,它将创建一个新实例,从而打破单例模式。
Java
// Java code to explain effect of
// Serialization on singleton classes
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInput;
import java.io.ObjectInputStream;
import java.io.ObjectOutput;
import java.io.ObjectOutputStream;
import java.io.Serializable;
class Singleton implements Serializable
{
// public instance initialized when loading the class
public static Singleton instance = new Singleton();
private Singleton()
{
// private constructor
}
}
public class GFG
{
public static void main(String[] args)
{
try
{
Singleton instance1 = Singleton.instance;
ObjectOutput out
= new ObjectOutputStream(new FileOutputStream("file.text"));
out.writeObject(instance1);
out.close();
// deserialize from file to object
ObjectInput in
= new ObjectInputStream(new FileInputStream("file.text"));
Singleton instance2 = (Singleton) in.readObject();
in.close();
System.out.println("instance1 hashCode:- "
+ instance1.hashCode());
System.out.println("instance2 hashCode:- "
+ instance2.hashCode());
}
catch (Exception e)
{
e.printStackTrace();
}
}
}
Output:-
instance1 hashCode:- 1550089733
instance2 hashCode:- 865113938
- 如您所见,两个实例的 hashCode 是不同的,因此有 2 个单例类的对象。因此,该类不再是单例。
克服序列化问题:-为了克服这个问题,我们必须实现方法 readResolve() 方法。
Java
// Java code to remove the effect of
// Serialization on singleton classes
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInput;
import java.io.ObjectInputStream;
import java.io.ObjectOutput;
import java.io.ObjectOutputStream;
import java.io.Serializable;
class Singleton implements Serializable
{
// public instance initialized when loading the class
public static Singleton instance = new Singleton();
private Singleton()
{
// private constructor
}
// implement readResolve method
protected Object readResolve()
{
return instance;
}
}
public class GFG
{
public static void main(String[] args)
{
try
{
Singleton instance1 = Singleton.instance;
ObjectOutput out
= new ObjectOutputStream(new FileOutputStream("file.text"));
out.writeObject(instance1);
out.close();
// deserialize from file to object
ObjectInput in
= new ObjectInputStream(new FileInputStream("file.text"));
Singleton instance2 = (Singleton) in.readObject();
in.close();
System.out.println("instance1 hashCode:- "
+ instance1.hashCode());
System.out.println("instance2 hashCode:- "
+ instance2.hashCode());
}
catch (Exception e)
{
e.printStackTrace();
}
}
}
Output:-
instance1 hashCode:- 1550089733
instance2 hashCode:- 1550089733
- 以上两个哈希码相同,因此不会创建其他实例。
- 克隆:克隆是一个创建重复对象的概念。使用 clone 我们可以创建对象的副本。假设,我们创建了一个单例对象的克隆,然后它会创建一个副本,其中有一个单例类的两个实例,因此该类不再是单例了。
Java
// JAVA code to explain cloning
// issue with singleton
class SuperClass implements Cloneable
{
int i = 10;
@Override
protected Object clone() throws CloneNotSupportedException
{
return super.clone();
}
}
// Singleton class
class Singleton extends SuperClass
{
// public instance initialized when loading the class
public static Singleton instance = new Singleton();
private Singleton()
{
// private constructor
}
}
public class GFG
{
public static void main(String[] args) throws CloneNotSupportedException
{
Singleton instance1 = Singleton.instance;
Singleton instance2 = (Singleton) instance1.clone();
System.out.println("instance1 hashCode:- "
+ instance1.hashCode());
System.out.println("instance2 hashCode:- "
+ instance2.hashCode());
}
}
Output :-
instance1 hashCode:- 366712642
instance2 hashCode:- 1829164700
- 两个不同的 hashCode 意味着有 2 个不同的单例类对象。
克服克隆问题:-为了克服这个问题,覆盖 clone() 方法并从克隆方法抛出异常 CloneNotSupportedException。现在每当用户尝试创建单例对象的克隆时,它都会抛出异常,因此我们的类保持单例。
Java
// JAVA code to explain overcome
// cloning issue with singleton
class SuperClass implements Cloneable
{
int i = 10;
@Override
protected Object clone() throws CloneNotSupportedException
{
return super.clone();
}
}
// Singleton class
class Singleton extends SuperClass
{
// public instance initialized when loading the class
public static Singleton instance = new Singleton();
private Singleton()
{
// private constructor
}
@Override
protected Object clone() throws CloneNotSupportedException
{
throw new CloneNotSupportedException();
}
}
public class GFG
{
public static void main(String[] args) throws CloneNotSupportedException
{
Singleton instance1 = Singleton.instance;
Singleton instance2 = (Singleton) instance1.clone();
System.out.println("instance1 hashCode:- "
+ instance1.hashCode());
System.out.println("instance2 hashCode:- "
+ instance2.hashCode());
}
}
Output:-
Exception in thread "main" java.lang.CloneNotSupportedException
at GFG.Singleton.clone(GFG.java:29)
at GFG.GFG.main(GFG.java:38)
- 现在我们已经停止用户创建单例类的克隆。如果你不想抛出异常,你也可以从 clone 方法返回相同的实例。
Java
// JAVA code to explain overcome
// cloning issue with singleton
class SuperClass implements Cloneable
{
int i = 10;
@Override
protected Object clone() throws CloneNotSupportedException
{
return super.clone();
}
}
// Singleton class
class Singleton extends SuperClass
{
// public instance initialized when loading the class
public static Singleton instance = new Singleton();
private Singleton()
{
// private constructor
}
@Override
protected Object clone() throws CloneNotSupportedException
{
return instance;
}
}
public class GFG
{
public static void main(String[] args) throws CloneNotSupportedException
{
Singleton instance1 = Singleton.instance;
Singleton instance2 = (Singleton) instance1.clone();
System.out.println("instance1 hashCode:- "
+ instance1.hashCode());
System.out.println("instance2 hashCode:- "
+ instance2.hashCode());
}
}
Output:-
instance1 hashCode:- 366712642
instance2 hashCode:- 366712642
- 现在,由于两个实例的哈希码相同,这意味着它们代表单个实例。