为什么核心 Java 库不使用枚举来实现单例模式?

Why does the core Java library NOT use enums for implementing the singleton pattern?

著名的BalusC答案()所指向的核心Java 类:

java.lang.Runtime#getRuntime()
java.awt.Desktop#getDesktop()
java.lang.System#getSecurityManager()

以上所有内容似乎 类 具有私有构造函数而不是枚举。如果枚举是实现单例模式的最佳实践 (Why is Enum best implementation for Singleton),为什么它没有在核心 Java 源代码中使用?

因为他们没有时光机。

enums 于 2004 年在 Java 5 "Tiger" 中引入。java.lang.runtime.Runtime#getRuntime() 于 1996 年在 Java 1.0 中引入。java.lang.runtime.System#getSecurityManager() 是1996 年在 Java 1.0 中引入。

java.awt.Desktop#getDesktop() 于 2006 年在 Java 6 "Mustang" 中引入,因此理论上可以使用 enum。你得问问设计师他们为什么选择这个特别的设计。

可能有很多原因。 Enum 是在 Java 1.5 中发布的,所以 Runtime 肯定早于它,将其更改为 Enum 可能会破坏现有代码,而 Java 不想破坏现有代码(泛型是一个很好的例子,说明我们必须跳过这些环节才能保持向后兼容性)。

对于像 Desktop 这样必须适应旧 API 的新 class,您还可以站点向后兼容。

我个人认为 Enum 非常有用,我倾向于像对待任何其他 class 一样对待它们(在我的个人项目中)(受其独特限制)。

枚举背后的语义

我认为我们不应该将 Java 枚举视为创建 class 的一个且唯一一个实例的独特方式,无论 Java 版本如何。
枚举传达一个含义:为特定实体定义的一组枚举值。
我不鼓励将其用作在其中执行大量逻辑(远程调用、持久性等...)的单例。
从语义上讲,当在某些代码中使用枚举时,代码的读者不会期望其中包含这样的逻辑。

这里使用的带有急切初始化的 Bill Pugg 习惯用法是枚举的一个很好的替代方法,例如 Runtime class :

private static Runtime currentRuntime = new Runtime();
private Runtime() {}

JDK5(出现枚举功能的版本)或更高版本源代码中的单例实现

与使用枚举模式相比,我发现没有枚举模式的单例实现的频率更高。

不使用枚举的一些单例示例 class:

Desktop(JDK 6) 。

public static synchronized Desktop getDesktop(){
   //...
   Desktop desktop = (Desktop)context.get(Desktop.class);
    if (desktop == null) {
        desktop = new Desktop();
        context.put(Desktop.class, desktop);
    }
  //...
}

不要只从那个案例中得出结论,因为这是一个延迟初始化的特例。

Collections (JDK 1.7) :

/**
 * Returns an enumeration that has no elements.  More precisely,
 *
  ...
 * @since 1.7
 */
@SuppressWarnings("unchecked")
public static <T> Enumeration<T> emptyEnumeration() {
    return (Enumeration<T>) EmptyEnumeration.EMPTY_ENUMERATION;
}

private static class EmptyEnumeration<E> implements Enumeration<E> {
    static final EmptyEnumeration<Object> EMPTY_ENUMERATION
        = new EmptyEnumeration<>();

    public boolean hasMoreElements() { return false; }
    public E nextElement() { throw new NoSuchElementException(); }
    public Iterator<E> asIterator() { return emptyIterator(); }
}

IsoChronology(JDK 8) :

public final class IsoChronology extends AbstractChronology implements Serializable {

    /**
     * Singleton instance of the ISO chronology.
     */
    public static final IsoChronology INSTANCE = new IsoChronology();
}

OptionalIntDeserializer(JDK 8) :

public class OptionalIntDeserializer extends BaseScalarOptionalDeserializer<OptionalInt>
{
    private static final long serialVersionUID = 1L;

    static final OptionalIntDeserializer INSTANCE = new OptionalIntDeserializer();

}

而且您很少会发现使用枚举模式实现的单例。

Comparators (JDK 1.8) :

enum NaturalOrderComparator implements Comparator<Comparable<Object>> {
    INSTANCE;

    @Override
    public int compare(Comparable<Object> c1, Comparable<Object> c2) {
        return c1.compareTo(c2);
    }

    @Override
    public Comparator<Comparable<Object>> reversed() {
        return Comparator.reverseOrder();
    }
}

我让你从这些例子中得出结论。

编写单例代码:通常是 anti-pattern

更进一步,单例是一个 anti-pattern 当它是通过明确地硬编码实现的:你可以用枚举来避免它,比如 old-fashioned 方式(静态急切或惰性单例)通过引入一个接口,它实现了 一个工厂方法来检索它。

但最后,所有这些都需要始终如一的严谨性(因此容易出错)并支持样板代码(因此会使您的代码变得不那么有意义)。
依赖注入解决了这个问题。所以我认为创建单例的最好方法是最终不显式创建单例。