通过enum方式的Singleton是否是懒惰初始化?

vas*_*ace 24 java singleton

这是一个非常广泛的枚举单例代码:

public enum enumClazz{
   INSTANCE
   enumClazz(){
     //do something
   }
}
Run Code Online (Sandbox Code Playgroud)

和一堆地方说这是一个懒惰的初始化.但在阅读" Java虚拟机内部"第7章- 类型的生命周期后,我感到困惑:

Java虚拟机规范为类和接口加载和链接的时序提供了实现的灵活性,但严格定义了初始化的时间.所有实现必须在其第一次活动使用时初始化每个类或接口.以下六种情况符合有效用途:

  • 创建一个类的新实例(在字节码中,执行新指令.或者,通过隐式创建,反射,克隆或反序列化.)
  • 调用由类声明的静态方法(在字节码中,执行invokestatic指令)
  • 使用或赋值由类或接口声明的静态字段,除了最终的静态字段并由编译时常量表达式初始化(​​在字节码中,执行getstatic或putstatic指令)
  • 在Java API中调用某些反射方法,例如Class类中的方法或java.lang.reflect包中的类
  • 初始化类的子类(类的初始化需要事先初始化其超类.)
  • 在Java虚拟机启动时将类指定为初始类(使用main()<方法)

粗体风格的第三点澄清了如果字段是static final,则字段的初始化发生在编译时.同样,INSTANCEin enumClazz隐含地等于public static final并符合第三点.

如果我的理解错了,有人可以纠正我吗?

Joa*_*uer 33

enum实例字段不是 "由编译时常量表达式初始化".它们不可能,因为只有String原始类型是编译时常量表达式的可能类型.

这意味着该类将在INSTANCE首次访问时初始化(这正是所需的效果).

上面的粗体文本中存在异常,因为这些常量(static final使用编译时常量表达式初始化的字段)将在编译期间有效地内联:

class A {
  public static final String FOO = "foo";

  static {
    System.out.println("initializing A");
  }
}

class B {
  public static void main(String[] args) {
    System.out.println(A.FOO);
  }
}
Run Code Online (Sandbox Code Playgroud)

B在此示例中执行类将不会初始化A(并且不会打印"初始化A").如果您查看为B您生成的字节码,您将看到一个字符串文字,其值为"foo",并且没有对该类的引用A.


ass*_*ias 5

大胆风格的第三点澄清,如果该字段是"静态最终",则该字段的初始化发生在编译时

不完全 - 它只适用于" 最终的静态字段并由编译时常量表达式初始化 ":

static final String = "abc"; //compile time constant
static final Object = new Object(); //initialised at runtime
Run Code Online (Sandbox Code Playgroud)

在您的情况下,单元将在加载枚举类时初始化,即第一次enumClazz在您的代码中引用.

所以它实际上是懒惰的,除非你在代码中使用枚举的其他地方有声明.

  • 像`Class &lt;?&gt; c = EnumClazz.class;`这样的语句不会触发`EnumClazz`的初始化。实际上,不实际使用它就很难触发它的初始化。值得强调的是* loading *和* initialization *是两个不同的东西。 (2认同)