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

问题描述

我看到answers like these,试图通过comments进行澄清,但对examples here不满意。

也许是时候回答这个特定问题了...

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

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 Lawrey Joachim Sauer )都同意枚举不是初始化的。关于延迟初始化的含义,第三个链接中的答案完全是错误的。

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

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

毫无疑问,如果枚举以任何方式延迟初始化,则枚举将是此列表中的另一个惯用法。实际上,事实并非如此,尽管如OP所示,对延迟初始化含义的混淆会导致一些错误的答案。

,

我可以下注吗?

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

  • 在某个时候类加载器将运行,并且您想知道在类加载器加载类时将执行什么 "things"
  • 另一方面,在类上调用方法将导致另一个 "thing" 运行/执行,您想确切地知道哪个(哪个 {{1 }} )将开始。

以下事实是相关的:

  • 静态初始化程序在类加载器加载类时运行。该类加载器将不会加载该类,直到 运行时需要加载它(因为方法或字段具有 被调用),例如: "processes"
  • 如果 EITHER 类的单例实例,则 OR 枚举类型具有 touchClass() ,该实例将在 field 该类的一部分,只要您“触摸” class-,因为类加载器会在加载时为类或枚举运行所有 static
  • 当方法调用询问类时,可能会发生延迟加载(这是我对您所要询问的“解释”) 创建一个单例实例-这可能会发生很多 static initializations class “加载”之后的时间。

如下所示的类:

enum

另一方面:

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