java中Enum的执行顺序

时间:2015-02-03 10:24:16

标签: java enums compilation initialization runtime

我有一个关于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;
    }
}

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

Enum看起来像'static final'Object,因为它的值永远不会改变。 因此,在此目的中,仅在编译时初始化是有效的。

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

4 个答案:

答案 0 :(得分:6)

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

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

并使用简单的测试代码,如

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

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

before enum
creating instace of NORMAL
creating instace of BOLD
creating instace of ITALIC
creating instace of UNDERLINE

表示当我们第一次使用枚举时,加载了enum类(并且已经初始化了它的静态字段)。

您也可以使用

Class.forName("full.packag.name.of.FontStyle");

如果尚未加载则导致其负载。

答案 1 :(得分:5)

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

答案很长: 枚举不是神奇的元素,但需要一些时间才能理解它们是如何工作的。枚举行为与class loading process相关,可以归纳为3个阶段:

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

为了理解枚举的工作原理,我们可以使用javap -c MyEnum对代码进行反编译。这将告诉我们:

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

    // 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
    }
    

    因此,枚举值是在执行静态初始化程序块时创建的,即在初始化阶段。这可以通过以下方法之一完成:

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

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

    • MyEnum.class.anyMethod()(当然除了getEnumConstants()):基本上我们只访问类metadatas,而不是实现
    • Class.forName("myPackage.MyEnum", false, aClassLoader)false value参数告诉类加载器避免初始化阶段
    • ClassLoader.getSystemClassLoader().loadClass("myPackage.MyEnum"):只显示 loading 阶段。

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

    • Class<MyEnum>.getInstance()抛出异常:因为枚举中没有公共构造函数
    • initialization blocks execution orders seems to be reversed from the usual one(第一个实例初始化程序block V1,然后是构造函数块constructor V1,然后是静态初始化程序static init):从反编译代码中,我们看到枚举值初始化需要放在静态初始化程序块的开头。对于每个枚举值,此静态初始值设定项创建一个新实例,该实例调用实例初始化程序块,然后调用构造函数块。静态初始化程序通过执行自定义静态初始化程序块来结束。

答案 2 :(得分:0)

当Enum类本身被加载时,枚举实例只创建一次。

创建它们一次非常重要,这样对象身份比较就可以了(==)。甚至必须调整object(de)序列化机制来支持这一点。

答案 3 :(得分:0)

枚举实例是在类链接(分辨率)期间创建的,这是类加载之后的阶段,就像&#34; normal&#的静态字段一样34;类。

类链接与类加载分开进行。因此,如果使用类加载器动态加载Enum类,则仅在实际尝试访问其中一个实例时才会实例化常量,例如,在使用getEnumConstants()中的方法Class时。 / p>

以下是测试上述断言的一些代码:

File1: TestEnum.java

public enum TestEnum {

    CONST1, CONST2, CONST3;

    TestEnum() {
        System.out.println( "Initializing a constant" );
    }
}

File2: Test.java

class Test
{
    public static void main( String[] args ) {

        ClassLoader cl = ClassLoader.getSystemClassLoader();

        try {
            Class<?> cls = cl.loadClass( "TestEnum" );
            System.out.println( "I have just loaded TestEnum" );
            Thread.sleep(3000);
            System.out.println( "About to access constants" );
            cls.getEnumConstants();
        } catch ( Exception e ) {
            e.printStackTrace();
            System.exit(1);
        }
    }
}

输出:

I have just loaded TestEnum
...暂停三秒......
About to access constants
Initializing a constant
Initializing a constant
Initializing a constant

如果因为任何原因你没有明确地使用枚举(只是通过引用它的一个常量)而是依赖于动态加载它,那么区别是很重要的。

注意:

  • 使用Class.forName()将加载和链接类,因此将立即实例化常量。
  • 只需要为要链接的整个类访问一个常量就足够了,因此当时将实例化所有常量。