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

Why enum singleton is lazy?

我看到了answers like these, tried to clarify via comments, and was unsatisfied by examples here

也许是时候提出这个具体问题了...

为什么枚举单例实现被称为 lazy?

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

它与 eager 实施有何不同?

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

现在,我们可以说两者都是懒惰。那么 eager 是什么?

很明显(例如)“双重检查锁定”方式实际上是懒惰的(而且混乱且缓慢)。但是如果枚举是惰性的,那么 any 单例由于不可避免的 class 加载也是惰性的——事实上,一切都是惰性的。这种区别在什么时候会失去意义?

我可以下注:

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

  • 在某些时候 class-loader 会 运行,您想知道 "things" 会在 class-loader 加载一个 class.
  • 在另一点调用 class 上的方法将导致另一个 "thing" 到 运行 / 执行,你想确切地知道哪个("processes")会开始..

以下事实是相关的:

  • Static initializers are run when the class-loader loads the class. The class-loader will not load the class until the code that is running encounters the need to load it (because a method or field has been invoked) such as: touchClass()
  • If a singleton instance of EITHER a class, OR an enumerated type has a field that is being initialized in the static part of the class it will be loaded as soon as you 'touch' the class - because the Class-Loader runs all static initializations for a class or enum on loading.
  • Lazy loading, likely, (And this is my "interpretation" about what you are asking) would happen when a method invokation asks the class to create a singleton instance - which could happen quite a bit of time after the "loading" of the class or enum.

A class 如下所示:

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

前两个 linked 答案(Peter LawreyJoachim Sauer)都同意枚举是 延迟初始化。第三个 link 中的答案完全错误地解释了惰性初始化的含义。

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

  • 如果需要在静态字段上使用惰性初始化来提高性能,请使用惰性初始化持有者 class 惯用语。
  • 如果您需要在实例字段上使用惰性初始化来提高性能,请使用 double-check 习惯用法。

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