java中Enum的执行顺序

Jun*_* Oh 6 java enums runtime compilation initialization

我有一个关于Enum的问题.

我有一个enum类,如下所示

public enum FontStyle {
    NORMAL("This font has normal style."),
    BOLD("This font has bold style."),
    ITALIC("This font has italic style."),
    UNDERLINE("This font has underline style.");

    private String description;

    FontStyle(String description) {
        this.description = description;
    }
    public String getDescription() {
        return this.description;
    }
}
Run Code Online (Sandbox Code Playgroud)

我想知道这个Enum对象何时被创建.

枚举看起来像'静态最终'对象,因为它的值永远不会改变.因此,在此目的中,仅在编译时初始化是有效的.

但它在顶层调用自己的构造函数,所以我怀疑它可以在我们调用它时生成,例如,在switch语句中.

Psh*_*emo 6

是的,枚举是静态常量,但不是编译时常量.就像任何其他类一样,在第一次需要时加载枚举.如果稍微改变它的构造函数,你可以很容易地观察它

FontStyle(String description) {
    System.out.println("creating instace of "+this);// add this
    this.description = description;
}
Run Code Online (Sandbox Code Playgroud)

并使用简单的测试代码

class Main {
    public static void main(String[] Args) throws Exception {
        System.out.println("before enum");
        FontStyle style1 = FontStyle.BOLD;
        FontStyle style2 = FontStyle.ITALIC;
    }
}
Run Code Online (Sandbox Code Playgroud)

如果您将运行main方法,您将看到输出

before enum
creating instace of NORMAL
creating instace of BOLD
creating instace of ITALIC
creating instace of UNDERLINE
Run Code Online (Sandbox Code Playgroud)

这表明当我们想第一次使用枚举时,enum类已被加载(并且其静态字段已被初始化).

你也可以使用

Class.forName("full.packag.name.of.FontStyle");
Run Code Online (Sandbox Code Playgroud)

如果尚未加载则导致其加载.


Jul*_*egg 5

TLDR:枚举值是在枚举类加载的初始化阶段在运行时创建的常量。这是有效的,因为枚举值仅创建一次。

长答案: 枚举不是神奇的元素,但是要花些时间才能理解它们是如何工作的。枚举行为与类加载过程有关,可以分为三个阶段:

  • loading:类字节码由类加载器加载
  • 链接:类层次结构已解决(有一个子阶段称为resolution
  • 初始化:通过调用静态初始化程序块来初始化类

让我们使用以下枚举类对此进行解释:

package mypackage;
public enum MyEnum {
    V1, V2;
    private MyEnum() {
        System.out.println("constructor "+this);
    }
    static {
        System.out.println("static init");
    }
    {
        System.out.println("block "+this);
    }
}
Run Code Online (Sandbox Code Playgroud)

为了了解它如何用于枚举,让我们使用来反编译代码javap -c MyEnum。这将使我们了解:

  1. 枚举实现为java.lang.Enum的子类
  2. 枚举值是类中的常量(即public static final值)
  3. 所有枚举值都是在静态初始化程序块的开头创建的,因此它们是在加载过程的初始化阶段创建的,因此是在字节码加载和依赖项链接阶段之后创建的。由于它们是在静态初始化程序块中创建的,因此只执行一次(而不是每次在开关中使用枚举时)。
  4. MyEnum.values() 返回所有枚举值的列表,作为枚举值数组的不可变副本。

反编译的代码如下:

// 1. an enum is implemented as a special class
public final class mypackage.MyEnum extends java.lang.Enum<mypackage.MyEnum> {
  public static final mypackage.MyEnum V1; // 2. enum values are constants of the enum class
  public static final mypackage.MyEnum V2;

  static {};
    Code: // 3. all enum values are created in the static initializer block
        // create the enum value V1
       0: new           #1                  // class mypackage/MyEnum
       3: dup
       4: ldc           #14                 // String V1
       6: iconst_0
       7: invokespecial #15                 // Method "<init>":(Ljava/lang/String;I)V
      10: putstatic     #19                 // Field V1:Lmypackage/MyEnum;

          // create the enum value V2
      13: new           #1                  // class mypackage/MyEnum
      16: dup
      17: ldc           #21                 // String V2
      19: iconst_1
      20: invokespecial #15                 // Method "<init>":(Ljava/lang/String;I)V
      23: putstatic     #22                 // Field V2:Lmypackage/MyEnum;

         // create an array to store all enum values
      39: iconst_2
      40: anewarray     #1                  // class mypackage/MyEnum

      43: dup
      44: iconst_0
      45: getstatic     #19                 // Field V1:Lmypackage/MyEnum;
      48: aastore

      49: dup
      50: iconst_1
      51: getstatic     #22                 // Field V2:Lmypackage/MyEnum;
      54: aastore

      61: putstatic     #27                 // Field ENUM$VALUES:[Lmypackage/MyEnum;

      64: getstatic     #29                 // Field java/lang/System.out:Ljava/io/PrintStream;
      67: ldc           #35                 // String "static init"
      69: invokevirtual #37                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      72: return

  public static mypackage.MyEnum[] values();
    Code:       // 4. it returns an immutable copy of the field ENUM$VALUES
       0: getstatic     #27                 // Field ENUM$VALUES:[Lmypackage/MyEnum;
       3: dup
       4: astore_0
       5: iconst_0
       6: aload_0
       7: arraylength
       8: dup
       9: istore_1
      10: anewarray     #1                  // class mypackage/MyEnum
      13: dup
      14: astore_2
      15: iconst_0
      16: iload_1
      17: invokestatic  #67                 // Method java/lang/System.arraycopy:(Ljava/lang/Object;ILjava/lang/Object;II)V  (=immutable copy)
      20: aload_2
      21: areturn

  public static mypackage.MyEnum valueOf(java.lang.String);
    Code:
       0: ldc           #1                  // class mypackage/MyEnum
       2: aload_0
       3: invokestatic  #73                 // Method java/lang/Enum.valueOf:(Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/Enum;
       6: checkcast     #1                  // class mypackage/MyEnum
       9: areturn
}
Run Code Online (Sandbox Code Playgroud)

因此,在执行静态初始化程序块时,即在初始化阶段,将创建枚举值。可以使用以下方法之一完成此操作:

  • 首次获得枚举值(例如System.out.println(MyEnum.V1)
  • 在执行枚举的静态方法时(例如MyEnum.valueOf()MyEnum.myStaticMethod()
  • Class.forName("mypackage.MyEnum")(执行加载链接初始化阶段)
  • 打电话时 MyEnum.class.getEnumConstants()

但是,枚举值不会通过以下操作初始化(该操作仅执行加载阶段,并且可能执行链接阶段):

  • MyEnum.class.anyMethod()getEnumConstants()当然除外):基本上,我们仅访问类元数据,而不访问实现
  • Class.forName("myPackage.MyEnum", false, aClassLoader)falsevalue参数告诉类加载器避免初始化阶段
  • ClassLoader.getSystemClassLoader().loadClass("myPackage.MyEnum"):明确地仅执行加载阶段。

关于枚举的一些有趣的事实:

  • Class<MyEnum>.getInstance() 引发异常:因为枚举中没有公共构造函数
  • 初始化块的执行次序似乎是从通常的一个逆转(第一实例初始化block V1,然后构造块constructor V1,那么静态初始化static init):从反编译的代码中,我们看到枚举值初始化发生在静态初始化块的开始。对于每个枚举值,此静态初始化程序创建一个新实例,该实例先调用实例初始化程序块,然后再调用构造函数块。静态初始化程序通过执行定制的静态初始化程序块结束。