我已经读到可以Singleton
使用Java 来实现Enum
:
public enum MySingleton {
INSTANCE;
}
但是,以上内容如何运作?具体来说,Object
必须实例化an 。在这里,如何MySingleton
实例化?谁在做什么new MySingleton()
?
INSTANCE
与相同public static final MySingleton INSTANCE = new MySingleton();
。
我已经读到可以Singleton
使用Java 来实现Enum
:
public enum MySingleton {
INSTANCE;
}
但是,以上内容如何运作?具体来说,Object
必须实例化an 。在这里,如何MySingleton
实例化?谁在做什么new MySingleton()
?
INSTANCE
与相同public static final MySingleton INSTANCE = new MySingleton();
。
Answers:
这个,
public enum MySingleton {
INSTANCE;
}
有一个隐式的空构造函数。相反,让它明确
public enum MySingleton {
INSTANCE;
private MySingleton() {
System.out.println("Here");
}
}
如果您随后使用main()
类似的方法添加了另一个类
public static void main(String[] args) {
System.out.println(MySingleton.INSTANCE);
}
你会看到
Here
INSTANCE
enum
字段是编译时间常数,但它们是其enum
类型的实例。并且,它们是在首次引用枚举类型时构造的。
private
修改已经没有意义了一个enum
构造函数,完全是多余的。
一种enum
类型是一种特殊类型的class
。
您enum
实际上将被编译为类似
public final class MySingleton {
public final static MySingleton INSTANCE = new MySingleton();
private MySingleton(){}
}
当您的代码首次访问时INSTANCE
,该类MySingleton
将由JVM加载并初始化。此过程一次(延迟)初始化static
上方的字段。
public enum MySingleton { INSTANCE,INSTANCE1; }
然后System.out.println(MySingleton.INSTANCE.hashCode()); System.out.println(MySingleton.INSTANCE1.hashCode());
打印不同的哈希码。这是否意味着创建了MySingleton的两个对象?
enum
常数。
在Joshua Bloch 撰写的Java最佳实践书中,您可以找到解释为什么要使用私有构造函数或Enum类型强制执行Singleton属性的原因。本章很长,因此请对其进行总结:
将类设为Singleton可能会使其难以测试其客户端,因为除非模拟实现一个用作其类型的接口,否则无法将模拟实现替换为Singleton。推荐的方法是通过简单地使一个枚举类型具有一个元素来实现Singletons:
// Enum singleton - the preferred approach
public enum Elvis {
INSTANCE;
public void leaveTheBuilding() { ... }
}
该方法在功能上与公共领域方法等效,除了它更简洁,免费提供序列化机制,并且即使面对复杂的序列化或反射攻击,也可以防止多重实例化。
尽管此方法尚未得到广泛采用,但是单元素枚举类型是实现单例的最佳方法。
像所有枚举实例一样,Java会在加载类时实例化每个对象,并保证每个JVM实例化该对象的次数仅为一次。将INSTANCE
声明视为一个公共的static final字段:Java将在首次引用该类时实例化该对象。
这些实例是在静态初始化期间创建的,该静态初始化在Java语言规范的12.4节中定义。
值得一提的是,Joshua Bloch将这种模式作为有效Java第二版的第 3项进行了详细描述。
由于Singleton Pattern是关于拥有私有构造函数并调用某种方法来控制实例化的(例如某些getInstance
),因此在Enums中我们已经有了一个隐式私有构造函数。
我不完全知道JVM或某个容器如何控制我们的实例Enums
,但似乎它已经使用了隐式Singleton Pattern
,不同之处在于我们不调用a getInstance
,而是调用Enum。
正如在某种程度上已经提到的,枚举是一个Java类,具有特殊条件,即其定义必须以至少一个“枚举常量”开头。
除此之外,枚举不能被扩展或不能用于扩展其他类,枚举是一个与任何类一样的类,您可以通过在常量定义下面添加方法来使用它:
public enum MySingleton {
INSTANCE;
public void doSomething() { ... }
public synchronized String getSomething() { return something; }
private String something;
}
您可以按照以下方式访问单例的方法:
MySingleton.INSTANCE.doSomething();
String something = MySingleton.INSTANCE.getSomething();
正如在其他答案中提到的那样,使用枚举而不是类,主要是关于单例的线程安全实例化,并保证它始终仅是一个副本。
而且,也许最重要的是,JVM本身和Java规范可以保证这种行为。
这是Java规范中关于如何防止枚举实例的多个实例的部分:
枚举类型除了由其枚举常量定义的实例外,没有其他实例。尝试显式实例化枚举类型是编译时错误。Enum中的最终克隆方法可确保永远不会克隆枚举常量,并且序列化机制的特殊处理可确保不会因反序列化而创建重复的实例。禁止枚举类型的反射实例化。总之,这四件事确保了枚举类型的实例不存在超出枚举常量定义的实例的情况。
值得注意的是,实例化之后,必须像在任何其他类中使用synced关键字等一样处理任何线程安全性问题。