我有一个关于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语句中。
答案 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个阶段:
让我们使用以下枚举类来解释这个:
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
对代码进行反编译。这将告诉我们:
public static final
值)
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
}
因此,枚举值是在执行静态初始化程序块时创建的,即在初始化阶段。这可以通过以下方法之一完成:
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()
抛出异常:因为枚举中没有公共构造函数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()
将加载和链接类,因此将立即实例化常量。