为什么枚举单例是懒惰的?



我看到了这样的答案,试图通过评论来澄清,但对这里的例子不满意。

也许是时候回答这个具体问题了...

为什么枚举单例实现称为惰性

public enum EnumLazySingleton {
INSTANCE;
EnumLazySingleton() {
System.out.println("constructing: " + this);
}
public static void touchClass() {}
}

它与急切实施有何不同?

public class BasicEagerSingleton {
private static final BasicEagerSingleton instance = new BasicEagerSingleton();
public static BasicEagerSingleton getInstance() {
return instance;
}
private BasicEagerSingleton() {
System.out.println("constructing: " + this);
}
public static void touchClass() {}
}

两者都将在不访问INSTANCE/getInstance()的情况下初始化实例 - 例如调用touchClass().

public class TestSingleton {
public static void main(String... args) {
System.out.println("sleeping for 5 sec...");
System.out.println("touching " + BasicEagerSingleton.class.getSimpleName());
BasicEagerSingleton.touchClass();
System.out.println("touching " + EnumLazySingleton.class.getSimpleName());
EnumLazySingleton.touchClass();
}
}

输出:

sleeping for 5 sec...
touching BasicEagerSingleton
constructing: BasicEagerSingleton@7bfcd12c
touching EnumLazySingleton
constructing: INSTANCE

现在,我们可以说两者都是懒惰的。那么什么是急切呢?

很明显(例如)"双重检查锁定"方式实际上是懒惰的(而且混乱且缓慢)。但是如果枚举是懒惰的,那么由于不可避免的类加载,任何单例都是懒惰的——事实上,一切都是懒惰的。在什么时候,这种区别将不再有意义?

前两个链接的答案(由Peter LawreyJoachim Sauer)都同意枚举不是懒惰初始化的。第三个链接中的答案对于延迟初始化的含义是完全错误的。

使用枚举作为单例的建议源自Josh Bloch的Effective Java。值得注意的是,关于枚举单例的章节没有提到懒惰。后面有一章专门讨论延迟初始化,同样没有提到枚举。本章包含两个亮点。

  • 如果需要使用延迟初始化来提高静态字段的性能,请使用延迟初始化持有者类习惯用法。
  • 如果您需要使用延迟初始化来提高实例字段的性能,请使用仔细检查习惯用法。

毫无疑问,如果枚举以任何方式延迟初始化,枚举将是此列表中的另一个成语。事实上,它们不是,尽管对延迟初始化含义的混淆会导致一些不正确的答案,如 OP 所示。

我可以打赌以下内容:

您正在尝试识别 2 个"进程"或......"things"(让我们让它变得容易理解 - 因为如果我开始说"代码块",听起来更难)......

在某个时候,类装入器将
  • 运行,并且您想知道当类装入器装入类时将执行什么"things"
  • 在另一点调用类上的方法将导致另一个"thing"运行/执行,并且您想知道哪个(哪个"processes")将启动。

以下事实相关:

  • 静态初始值设定项在类装入器装入类时运行。 类装入器不会装入该类,直到 运行会遇到加载它的需要(因为方法或字段具有 被调用),例如:touchClass()
  • 如果类枚举类型的单例实例具有在类的static部分中初始化的field,则一旦您"触摸"该类 -因为类加载器在装入时运行类或枚举的所有static initializations
  • 延迟加载,很可能,(这是我对你所问内容的"解释")会在方法调用询问类时发生 创建一个单一实例 -这可能会发生很多classenum"加载"后的时间.

如下所示的类:

public class LazySingleton
{
// At time of class-loading, this singleton is set to 'null'
private static singleton = null;
// This is a method that will not be invoked until it is called by
// some other code-block (some other "thing")...  When "touchClass()"
// is called, the singleton instance is not created.
public static LazySingleton retrieveSingleton()
{
if (singleton == null) singleton = new LazySingleton();
return singleton;
}
// DOES NOTHING...  The Singleton is *not* loaded, even though the
// Class Loader has already loaded this Java ".class" file
// into memory.
public static void touchClass() { }
private LazySingleton()
{ System.out.println("constructing: LazySingleton"); }
}

另一方面:

public enum EagerEnum
{
// The class loader will run this constructor as soon as this 'enum'
// is loaded from a '.class' file (in JAR or on disk) into memory
MyEnumConstant();
private EagerEnum()
{ System.out.println("Eager Enum Constructed"); }
// This will cause the Class Loader to Load this enum from the
// Java ".class" File immediately, and the "MyEnumConstant" will
// also have to be loaded - meaning the constructor will be called.
public static void touchEnum() { }
}

所以下面的代码将产生输出

LazySingleton.touchClass();            // Prints nothing
EagerEnum.touchClass();                // Prints "Eager Enum Constructed"
LazySingleton.getSingletonInstance();  // Prints "constructing: LazySingleton

最新更新