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



著名的BalusC答案(https://stackoverflow.com/a/2707195)所指出的核心Java类:

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

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

枚举背后的语义

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

这里使用的带有急切初始化的比尔·帕格成语是枚举的公平替代方案,例如在类Runtime

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

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

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

没有枚举使用的单例类的一些例子:

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();
}
}

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

编码单例:通常是反模式

更进一步,单例是一种反模式,当它通过显式硬编码来实现时:你可以通过引入它实现的接口工厂方法来检索它,像老式方式(静态渴望或懒惰单例)一样使用枚举来避免它。

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

因为他们没有时间机器。

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"中引入的,因此理论上可以使用enums。你必须问设计师为什么选择这个特定的设计。

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

对于必须适应旧 API 的新类(如Desktop),您还可以站点向后兼容。

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

相关内容

  • 没有找到相关文章