单例通过枚举方式是懒惰初始化的吗?

2022-09-01 14:22:26

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

public enum enumClazz{
   INSTANCE
   enumClazz(){
     //do something
   }
}

还有一堆地方说这是一个懒惰的初始化。但是,在我阅读了“Java虚拟机内部”的第7章 - 一种类型的生命周期之后,我感到困惑:

Java 虚拟机规范在类和接口加载和链接的计时方面为实现提供了灵活性,但严格定义了初始化的时序。所有实现都必须在首次使用每个类或接口时对其进行初始化。以下六种情况符合主动用途的条件:

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

第三点用粗体字澄清,如果字段是,字段的初始化发生在编译时。同样,in 隐含地等于并符合第三点。static finalINSTANCEenumClazzpublic static final

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


答案 1

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

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


答案 2

粗体样式的第三点阐明,如果字段是“静态最终”,则字段的初始化发生在符合时间

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

static final String = "abc"; //compile time constant
static final Object = new Object(); //initialised at runtime

在你的例子中,单例将在加载枚举类时初始化,即第一次在你的代码中被引用。enumClazz

因此,它实际上是懒惰的,除非你在代码中的其他地方有一个使用枚举的语句。


推荐