用枚举实现Singleton(在Java中)


176

我已经读到可以Singleton使用Java 来实现Enum

public enum MySingleton {
     INSTANCE;   
}

但是,以上内容如何运作?具体来说,Object必须实例化an 。在这里,如何MySingleton实例化?谁在做什么new MySingleton()


24
谁在做新的MySingleton() JVM。
Sotirios Delimanolis 2014年

37
INSTANCE与相同public static final MySingleton INSTANCE = new MySingleton();
Pshemo

6
枚举-有保证的单例。
不是错误

阅读dzone.com/articles/java-singletons-using-enum ,为什么要使用枚举而不是其他方法。简短:使用序列化和反射时问题开始。
宫城县

Answers:


202

这个,

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类型的实例。并且,它们是在首次引用枚举类型时构造的。


13
您应该添加默认情况下,枚举具有隐式的私有构造函数,并且除非明确需要在该构造函数中运行的代码,否则不需要显式添加私有构造函数
Nimrod Dayan 2016年

每个枚举字段仅创建一次实例,因此无需创建私有构造函数。
Pravat Panda

2
公共枚举MySingleton {INSTANCE,INSTANCE1; },然后是System.out.println(MySingleton.INSTANCE.hashCode()); System.out.println(MySingleton.INSTANCE1.hashCode()); 它输出不同的哈希码。这是否意味着创建了MySingleton的两个对象?
斯科特里程

@scottmiles是的。因为您有两个实例。根据定义,一个单例有一个。
艾略特·弗里施

private修改已经没有意义了一个enum构造函数,完全是多余的。
Dmitri Nesteruk

75

一种enum类型是一种特殊类型的class

enum实际上将被编译为类似

public final class MySingleton {
    public final static MySingleton INSTANCE = new MySingleton();
    private MySingleton(){} 
}

当您的代码首次访问时INSTANCE,该类MySingleton将由JVM加载并初始化。此过程一次(延迟)初始化static上方的字段。


此类也应包含私有的constructor()吗?我认为应该是
Yasir Shabbir Choudhary

public enum MySingleton { INSTANCE,INSTANCE1; }然后System.out.println(MySingleton.INSTANCE.hashCode()); System.out.println(MySingleton.INSTANCE1.hashCode());打印不同的哈希码。这是否意味着创建了MySingleton的两个对象?
斯科特英里

2
@scottmiles是的,那只是两个不同的enum常数。
Sotirios Delimanolis


@SotiriosDelimanolis,这种使用枚举线程的方法安全吗?
Abg

63

在Joshua Bloch 撰写Java最佳实践书中,您可以找到解释为什么要使用私有构造函数或Enum类型强制执行Singleton属性的原因。本章很长,因此请对其进行总结:

将类设为Singleton可能会使其难以测试其客户端,因为除非模拟实现一个用作其类型的接口,否则无法将模拟实现替换为Singleton。推荐的方法是通过简单地使一个枚举类型具有一个元素来实现Singletons:

// Enum singleton - the preferred approach
public enum Elvis {
INSTANCE;
public void leaveTheBuilding() { ... }
}

该方法在功能上与公共领域方法等效,除了它更简洁,免费提供序列化机制,并且即使面对复杂的序列化或反射攻击,也可以防止多重实例化。

尽管此方法尚未得到广泛采用,但是单元素枚举类型是实现单例的最佳方法。


我认为,要使单例成为可测试的,您可以使用策略模式,并使所有单例的行为都通过该策略。因此,您可以有一个“生产”策略和一个“测试”策略...
Erk

9

像所有枚举实例一样,Java会在加载类时实例化每个对象,并保证每个JVM实例化该对象的次数仅为一次。将INSTANCE声明视为一个公共的static final字段:Java将在首次引用该类时实例化该对象。

这些实例是在静态初始化期间创建的,该静态初始化在Java语言规范的12.4节中定义。

值得一提的是,Joshua Bloch将这种模式作为有效Java第二版 3项进行了详细描述。


6

由于Singleton Pattern是关于拥有私有构造函数并调用某种方法来控制实例化的(例如某些getInstance),因此在Enums中我们已经有了一个隐式私有构造函数。

我不完全知道JVM或某个容器如何控制我们的实例Enums,但似乎它已经使用了隐式Singleton Pattern,不同之处在于我们不调用a getInstance,而是调用Enum。


3

正如在某种程度上已经提到的,枚举是一个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关键字等一样处理任何线程安全性问题。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.