我已经读过可以使用Singleton
在Java中实现Enum
,例如:
public enum MySingleton {
INSTANCE;
}
但是,上面的工作如何?具体而言,必须实例化Object
。这里,MySingleton
如何被实例化?谁在做new MySingleton()
?
答案 0 :(得分:180)
此,
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
类型的实例。并且,当enum类型首次引用 时构建它们。
答案 1 :(得分:66)
enum
类型是class
的特殊类型。
您的enum
实际上会被编译为类似
public final class MySingleton {
public final static MySingleton INSTANCE = new MySingleton();
private MySingleton(){}
}
当您的代码首次访问INSTANCE
时,类MySingleton
将由JVM加载并初始化。此过程初始化上方的static
字段(懒惰)。
答案 2 :(得分:57)
在Joshua Bloch的Java best practices book中,您可以找到解释为什么要使用私有构造函数或Enum类型强制执行Singleton属性的原因。这一章很长,所以总结一下:
使类成为Singleton会使测试其客户端变得困难,因为除非它实现了作为其类型的接口,否则不可能将模拟实现替换为单例。 推荐的方法是通过简单地使用一个元素创建一个枚举类型来实现单例:
// Enum singleton - the preferred approach
public enum Elvis {
INSTANCE;
public void leaveTheBuilding() { ... }
}
除了它之外,这种方法在功能上等同于公共领域方法 更简洁,免费提供序列化机制,并提供 铁定的保证反对多重实例化,即使面对复杂 序列化或反射攻击。
虽然这种方法尚未广泛应用 采用单元素枚举类型是实现单例的最佳方式。
答案 3 :(得分:9)
与所有枚举实例一样,Java在加载类时实例化每个对象,并保证每个JVM实例化一次。将INSTANCE
声明视为公共静态最终字段:Java将在第一次引用该类时实例化该对象。
实例是在静态初始化期间创建的,静态初始化在Java Language Specification, section 12.4。
中定义对于它的价值,Joshua Bloch详细描述了这种模式,作为Effective Java Second Edition的第3项。
答案 4 :(得分:4)
由于 Singleton Pattern 是关于拥有一个私有构造函数并调用一些方法来控制实例化(比如某些getInstance
),所以在Enums中我们已经有了一个隐式的私有构造函数。
我并不完全知道 JVM 或某些容器如何控制Enums
的实例,但它似乎已经使用了隐式Singleton Pattern
,不同之处在于我们不会调用getInstance
,我们只需要调用枚举。
答案 5 :(得分:2)
在某种程度上已经提到过,枚举是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 specification中关于如何阻止枚举实例的多个实例的部分:
枚举类型除了由其枚举常量定义的实例外,没有其他实例。尝试显式实例化枚举类型是编译时错误。 Enum中的最终克隆方法可确保永远不会克隆枚举常量,并且序列化机制的特殊处理可确保不会因反序列化而创建重复的实例。禁止枚举类型的反射实例化。总之,这四件事确保了没有枚举类型的实例存在,除了由枚举常量定义的实例之外。
值得注意的是,实例化之后,必须像在任何其他类中使用synced关键字等一样处理任何线程安全性问题。