我读过,在Java中使用枚举实现单例是可能的,例如:

public enum MySingleton {
     INSTANCE;   
}

但是,上面是如何工作的呢?具体来说,Object必须被实例化。这里,MySingleton是如何被实例化的?谁正在执行new MySingleton()?


当前回答

在某种程度上,前面已经提到,枚举是具有特殊条件的java类,其定义必须以至少一个“enum常量”开始。

除此之外,枚举不能被扩展或用于扩展其他类,枚举是一个像任何类一样的类,你可以通过在常量定义下面添加方法来使用它:

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规范中关于如何防止枚举实例的多个实例的部分:

An enum type has no instances other than those defined by its enum constants. It is a compile-time error to attempt to explicitly instantiate an enum type. The final clone method in Enum ensures that enum constants can never be cloned, and the special treatment by the serialization mechanism ensures that duplicate instances are never created as a result of deserialization. Reflective instantiation of enum types is prohibited. Together, these four things ensure that no instances of an enum type exist beyond those defined by the enum constants.

值得注意的是,在实例化之后,任何线程安全问题都必须像在任何其他类中使用synchronized关键字等一样处理。

其他回答

这一点,

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字段是编译时常量,但它们是其枚举类型的实例。并且,它们是在第一次引用枚举类型时构造的。

因为单例模式是关于拥有一个私有构造函数并调用一些方法来控制实例化(比如一些getInstance),所以在Enums中我们已经有了一个隐式私有构造函数。

我不知道JVM或一些容器是如何控制我们的枚举实例的,但它似乎已经使用了隐式的单例模式,不同的是我们不调用getInstance,我们只是调用Enum。

在某种程度上,前面已经提到,枚举是具有特殊条件的java类,其定义必须以至少一个“enum常量”开始。

除此之外,枚举不能被扩展或用于扩展其他类,枚举是一个像任何类一样的类,你可以通过在常量定义下面添加方法来使用它:

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规范中关于如何防止枚举实例的多个实例的部分:

An enum type has no instances other than those defined by its enum constants. It is a compile-time error to attempt to explicitly instantiate an enum type. The final clone method in Enum ensures that enum constants can never be cloned, and the special treatment by the serialization mechanism ensures that duplicate instances are never created as a result of deserialization. Reflective instantiation of enum types is prohibited. Together, these four things ensure that no instances of an enum type exist beyond those defined by the enum constants.

值得注意的是,在实例化之后,任何线程安全问题都必须像在任何其他类中使用synchronized关键字等一样处理。

枚举类型是类的一种特殊类型。

你的枚举实际上会被编译成

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

当您的代码第一次访问INSTANCE时,MySingleton类将由JVM加载并初始化。这个过程会初始化上面的静态字段一次(惰性地)。

与所有枚举实例一样,Java在装入类时实例化每个对象,并保证每个JVM只实例化一次。将INSTANCE声明视为一个公共静态final字段:Java将在第一次引用该类时实例化该对象。

实例是在静态初始化过程中创建的,静态初始化在Java语言规范第12.4节中定义。

值得注意的是,Joshua Bloch在Effective Java第二版的第3条中详细描述了这种模式。