简体   繁体   English

java中Enum的执行顺序

[英]Execution order of Enum in java

I got a question about Enum. 我有一个关于Enum的问题。

I have an enum class looks like below 我有一个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;
    }
}

I wonder when this Enum object is created. 我想知道这个Enum对象何时被创建。

Enum looks like 'static final' Object since its value will never changed. 枚举看起来像'静态最终'对象,因为它的值永远不会改变。 So in that purpose, it is efficient to initialize in compile time only. 因此,在此目的中,仅在编译时初始化是有效的。

But it calls its own constructor in top, so I doubt that it could generate whenever we call it, for example, in switch statement. 但它在顶层调用自己的构造函数,所以我怀疑它可以在我们调用它时生成,例如,在switch语句中。

Yes, enums are static constants but are not compile time constants. 是的,枚举是静态常量,但不是编译时常量。 Just like any other classes enum is loaded when first time needed. 就像任何其他类一样,在第一次需要时加载枚举。 You can observe it easily if you change its constructor a little 如果稍微改变它的构造函数,你可以很容易地观察它

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

and use simple test code like 并使用简单的测试代码

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

If you will run main method you will see output 如果您将运行main方法,您将看到输出

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

which shows that enum class was loaded (and its static fields have been initialized) right when we wanted to use enum first time. 这表明当我们想第一次使用枚举时,enum类已被加载(并且其静态字段已被初始化)。

You can also use 你也可以使用

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

to cause its load if it wasn't loaded yet. 如果尚未加载则导致其加载。

TLDR: enum values are constants created once at runtime, during the initialization phase of the enum class loading. TLDR:枚举值是在运行时,在枚举类加载的初始化阶段创建的常量。 This is efficient as the enum values are only created once. 这是有效的,因为枚举值仅创建一次。

Long answer: Enums are not magical elements, but it takes some time to understand how they work. 答案长:枚举不是神奇的元素,但要了解它们是如何工作需要一些时间。 The enum behavior is related to the class loading process which can be summarized by 3 phases: 枚举行为与类加载过程有关 ,可以归纳为3个阶段:

  • loading : the class bytecode is loaded by the classloader loading :类字节码由类加载器加载
  • linking : the class hierarchy is resolved (there is a subphase called resolution ) 链接 :解析类层次结构(有一个称为解析的子阶段)
  • initializing : the class is initialized by calling the static initializer blocks 初始化 :通过调用静态初始化程序块来初始化类

Let's explain this using the following enum class: 让我们使用以下枚举类来解释这个:

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

In order to understand how it works for enums, lets decompile the code using javap -c MyEnum . 为了理解它如何用于枚举,让我们使用javap -c MyEnum反编译代码。 This will learn us that: 这将告诉我们:

  1. an enum is implemented as a subclass of java.lang.Enum 枚举是作为java.lang.Enum的子类实现的
  2. enum values are constants (ie public static final values) in the class 枚举值是类中的常量(即public static final值)
  3. all the enum values are created in at the beginning of static initializer block, thus they are created in the initialize phase of the loading process, so after the bytecode loading and dependencies linking phases. 在静态初始化块的开头都创建了所有的枚举值中,因此它们在加载过程的初始化阶段创建的,因此字节码装载和依赖关系链接阶段之后。 As they are created in the static initializer block, it is executed only once (and not every time we use the enum in a switch). 因为它们是在静态初始化程序块中创建的,所以它只执行一次(而不是每次我们在交换机中使用枚举时)。
  4. MyEnum.values() returns the list of all enum values as an immutable copy of the enum values array. MyEnum.values()返回所有枚举值的列表,作为枚举值数组的不可变副本。

The decompiled code is the following: 反编译代码如下:

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

Consequently, the enum values are created when the static initializer block is executed, that is in the initialization phase. 因此,枚举值是在执行静态初始化程序块时创建的,即在初始化阶段。 This can be done with one of the following methods: 这可以使用以下方法之一完成:

  • the first time an enum value is obtained (eg System.out.println(MyEnum.V1) ) 第一次获得枚举值(例如System.out.println(MyEnum.V1)
  • when executing a static method of the enum (eg MyEnum.valueOf() or MyEnum.myStaticMethod() ) 执行枚举的静态方法时(例如MyEnum.valueOf()MyEnum.myStaticMethod()
  • with Class.forName("mypackage.MyEnum") (which does the loading , linking and initializing phases) 使用Class.forName("mypackage.MyEnum") (执行加载链接初始化阶段)
  • when calling MyEnum.class.getEnumConstants() 调用MyEnum.class.getEnumConstants()

However the enum values will NOT be initialized with the following operation (which do only the loading phase, and potentially the linking phase): 但是,枚举值不会通过以下操作初始化(仅执行加载阶段,可能还有链接阶段):

  • MyEnum.class.anyMethod() (except getEnumConstants() of course): basically we access only the class metadatas, not the implementation MyEnum.class.anyMethod() (当然除了getEnumConstants() ):基本上我们只访问类metadatas,而不是实现
  • Class.forName("myPackage.MyEnum", false, aClassLoader) : the false value parameter tells the classloader to avoid the initialization phase Class.forName("myPackage.MyEnum", false, aClassLoader)false值参数告诉类加载器避免初始化阶段
  • ClassLoader.getSystemClassLoader().loadClass("myPackage.MyEnum") : explicitely does only the loading phase. ClassLoader.getSystemClassLoader().loadClass("myPackage.MyEnum") :只显示加载阶段。

Some fun other facts about enums: 一些有趣的关于枚举的其他事实:

  • Class<MyEnum>.getInstance() throws an Exception: because there is no public constructor in the enum Class<MyEnum>.getInstance()抛出异常:因为枚举中没有公共构造函数
  • the initialization blocks execution orders seems to be reversed from the usual one (first instance initializer block V1 , then constructor block constructor V1 , then static initializer static init ): from the decompiled code, we saw that enum values initialization takes place on the beginning of the static initializer block. 初始化块的执行顺序似乎与通常的一样 (第一个实例初始化程序block V1 ,然后是构造函数块constructor V1 ,然后是静态初始化程序static init ):从反编译代码中,我们看到枚举值初始化发生在开头的静态初始化块。 For each enum value, this static initializer create a new instance, which calls the instance initializer block, then the constructor block. 对于每个枚举值,此静态初始值设定项创建一个新实例,该实例调用实例初始化程序块,然后调用构造函数块。 The static initializer ends by executing the custom static initializer block. 静态初始化程序通过执行自定义静态初始化程序块来结束。

The enum instances are created only once, when the Enum class itself is loaded. 枚举实例仅在加载Enum类时创建一次。

It is very important that they are created only once, so that object identity comparison works ( == ). 非常重要的是它们只被创建一次,因此对象标识比较起作用( == )。 Even the object (de)serialization mechanism had to be adjusted to support this. 甚至必须调整object(de)序列化机制来支持这一点。

Enum instances are created during class linking (resolution) , which is a stage that comes after class loading , just like static fields of a "normal" class. 枚举实例是在类链接(分辨率)期间创建的,这是一个在类加载之后的阶段,就像“普通”类的静态字段一样。

Class linking happens separately from class loading. 类链接与类加载分开进行。 So if you dynamically load the Enum class using a class loader, the constants will be instantiated only when you actually try to access one of the instances, for example, when using the method getEnumConstants() from Class . 因此,如果使用类加载器动态加载Enum类,则仅在实际尝试访问其中一个实例时才会实例化常量,例如,在使用Class的方法getEnumConstants()时。

Here is a bit of code to test the above assertion: 这里有一些代码来测试上面的断言:

File1: TestEnum.java File1: TestEnum.java

public enum TestEnum {

    CONST1, CONST2, CONST3;

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

File2: Test.java 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);
        }
    }
}

The output: 输出:

I have just loaded TestEnum
... three seconds pause ... ......暂停三秒......
 About to access constants 即将访问常量\nInitializing a constant 初始化常量\nInitializing a constant 初始化常量\nInitializing a constant 初始化常量\n

The distinction is important if, for any reason, you are not using the enum plainly (just by referring to one of its constants) but instead rely on dynamically loading it. 如果由于任何原因,您没有明确地使用枚举(只是通过引用其常量之一)而是依赖于动态加载它,则区别很重要。

Notes: 笔记:

  • Using Class.forName() will both load and link the class, so the constants will be immediately instantiated. 使用Class.forName()将加载和链接类,因此将立即实例化常量。
  • It is enough to access one constant for the entire class to be linked, therefore all the constants will be instantiated at that time. 只需要为要链接的整个类访问一个常量就足够了,因此所有常量都将在那时实例化。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM