[英]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个阶段:
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: 这将告诉我们:
public static final
values) in the class public static final
值) 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:
这可以使用以下方法之一完成:
System.out.println(MyEnum.V1)
) System.out.println(MyEnum.V1)
) MyEnum.valueOf()
or MyEnum.myStaticMethod()
) MyEnum.valueOf()
或MyEnum.myStaticMethod()
) Class.forName("mypackage.MyEnum")
(which does the loading , linking and initializing phases) Class.forName("mypackage.MyEnum")
(执行加载 , 链接和初始化阶段) 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()
抛出异常:因为枚举中没有公共构造函数 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 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: 笔记:
Class.forName()
will both load and link the class, so the constants will be immediately instantiated. Class.forName()
将加载和链接类,因此将立即实例化常量。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.