枚举类型是一种语法糖:它只是一种语法,是为了方便程序的编写,在编译器编译之后,它将被解读成标准的class类。也就是说,枚举仅仅是披在java语法上的美丽外衣。
接下来我将根据一个简单的枚举类,通过分析它的反编译的class文件来理解枚举类的实质。
1. 枚举可以这样理解:
如果你想创建这样一个类:我只想new固定数量的实例对象,且不能多也不能少。
那么你可能就需要使用枚举类型。
枚举是满足这样要求的特殊类:它有固定大小的实例对象,且不能在外部new新对象,因为他的构造器是private私有的。
枚举将它自身的实例化对象,包含在了自身的字段里,包裹起来,并被构造成公有的(public),静态的(static),且不能改变的(fianl)对象。
2. 如下面这个枚举类:
public enum TestEnum{
ONE("one"),TWO("two"),TREE("tree");
private String name;
private TestEnum(String name){
this.name=name;
}
public void show(){
System.out.println(name);
};
public static void main(String[] args){
TestEnum e0=TestEnum.ONE;
TestEnum e1=TestEnum.TWO;
TestEnum e2=TestEnum.TREE;
e0.show();
e1.show();
e2.show();
}
}
我们可以使用javap反编译出它的字节码来分析,我将它放在了文章末尾。
3. 分析它的反编译文件:
-
首先从下面这行可以看出,枚举最终会被编译成一个普通的class类,他继承自Enum这个抽象类。
并且它是:ACC_FINAL,代表不能被继承。
Enum的定义如下:
public abstract class Enum<E extends Enum<E>> extends Object implements Comparable<E>, Serializable {//...}
-
从下图可以看出,枚举是将他的常量编译成自身的对象,并且用public ,static ,final修饰,包裹在自身,作为一个字段。
-
如果你去关注过Enum类的源码,你会发现,其中并没有我们使用枚举时经常用到的values(),valueOf() 方法。那我们到底调用的什么呢?Enum里没写,我们自己肯定也没写,那函数去哪儿了呢?
答案是:编译器会自动生成这两个函数,如下图
在values()函数的Code属性中可以看到
0: getstatic #1 //Field $VALUES:[LTestEnum
它的作用就是获得 $VALUES 这个引用指向的数组,并压入栈顶,这个引用也是编译器自动添加的,可以从后面的static{}中看到它如何初始化的,这个后面会讲到。
3: invokevirtual #2 // Method "[LTestEnum;".clone:() Ljava/lang/Object;
调用$VALUES数组的clone()方法,这是Object继承的方法。
6:checkcast #3 //class "[LTestEnum;"
检查上面复制的数组是否是TestEnum[] 类型,如果不是抛出ClassCastException。
对于valueOf()方法,大致也是调用Enum的valueOf()方法来获得制定名称的TestEnum对象。 -
最后的关键是static{}这个静态代码块,它也是编译器自动生成,对于TestEnum类来说,它的作用有三个:
- 创建ONE,TWO,TREE三个实例对象,并初始化。
9: invokespecial #16
这句话的作用就是调用Enum类的实例构造器,传入对象的名称ONE,以及它的序数0,以及他的值one。
12:putstatic #10
作用是将刚创建的对象赋值给字段ONE。 - 创建并初始化隐藏的字段$VALUES,上边说过,这是存储枚举常量的数组,它是TestEnum[]类型。
45:iconst_3
将int类型的3压入栈顶,这个数,作为下一句创建数组的大小。
46:anewarray #4 //class TestEnum
创建一个TestEnum[] 数组引用,并压入栈顶,大小为3
第49-54,55-60,61-66含义是完全一样的,它们的作用就是获得静态字段,放入到数组指定的位置中。 - 将刚才创建的数组赋值给$VALUSE(),并返回
- 创建ONE,TWO,TREE三个实例对象,并初始化。
4. 反编译文件中一些访问标识和标志
- 访问标志:主要用在类,接口,枚举等类型的 flags属性。
- 描述符标识字符含义:主要用来描述方法的返回值类型,参数类型。
- 字段的访问标志:主要用在字段的flags属性。
TestEnum类的反编译全文:
C:\Users\69173\Desktop\java语法糖\枚举类>javap -verbose TestEnum.class
Classfile /C:/Users/69173/Desktop/java语法糖/枚举类/TestEnum.class
Last modified 2019-10-5; size 1272 bytes
MD5 checksum 049b33fef1ba9ac5d6dd7d196b9c7cb1
Compiled from "TestEnum.java"
public final class TestEnum extends java.lang.Enum<TestEnum>
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_FINAL, ACC_SUPER, ACC_ENUM
Constant pool:
#1 = Fieldref #4.#48 // TestEnum.$VALUES:[LTestEnum;
#2 = Methodref #49.#50 // "[LTestEnum;".clone:()Ljava/lang/Object;
#3 = Class #29 // "[LTestEnum;"
#4 = Class #51 // TestEnum
#5 = Methodref #21.#52 // java/lang/Enum.valueOf:(Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/Enum;
#6 = Methodref #21.#53 // java/lang/Enum."<init>":(Ljava/lang/String;I)V
#7 = Fieldref #4.#54 // TestEnum.name:Ljava/lang/String;
#8 = Fieldref #55.#56 // java/lang/System.out:Ljava/io/PrintStream;
#9 = Methodref #57.#58 // java/io/PrintStream.println:(Ljava/lang/String;)V
#10 = Fieldref #4.#59 // TestEnum.ONE:LTestEnum;
#11 = Fieldref #4.#60 // TestEnum.TWO:LTestEnum;
#12 = Fieldref #4.#61 // TestEnum.TREE:LTestEnum;
#13 = Methodref #4.#62 // TestEnum.show:()V
#14 = String #22 // ONE
#15 = String #63 // one
#16 = Methodref #4.#64 // TestEnum."<init>":(Ljava/lang/String;ILjava/lang/String;)V
#17 = String #24 // TWO
#18 = String #65 // two
#19 = String #25 // TREE
#20 = String #66 // tree
#21 = Class #67 // java/lang/Enum
#22 = Utf8 ONE
#23 = Utf8 LTestEnum;
#24 = Utf8 TWO
#25 = Utf8 TREE
#26 = Utf8 name
#27 = Utf8 Ljava/lang/String;
#28 = Utf8 $VALUES
#29 = Utf8 [LTestEnum;
#30 = Utf8 values
#31 = Utf8 ()[LTestEnum;
#32 = Utf8 Code
#33 = Utf8 LineNumberTable
#34 = Utf8 valueOf
#35 = Utf8 (Ljava/lang/String;)LTestEnum;
#36 = Utf8 <init>
#37 = Utf8 (Ljava/lang/String;ILjava/lang/String;)V
#38 = Utf8 Signature
#39 = Utf8 (Ljava/lang/String;)V
#40 = Utf8 show
#41 = Utf8 ()V
#42 = Utf8 main
#43 = Utf8 ([Ljava/lang/String;)V
#44 = Utf8 <clinit>
#45 = Utf8 Ljava/lang/Enum<LTestEnum;>;
#46 = Utf8 SourceFile
#47 = Utf8 TestEnum.java
#48 = NameAndType #28:#29 // $VALUES:[LTestEnum;
#49 = Class #29 // "[LTestEnum;"
#50 = NameAndType #68:#69 // clone:()Ljava/lang/Object;
#51 = Utf8 TestEnum
#52 = NameAndType #34:#70 // valueOf:(Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/Enum;
#53 = NameAndType #36:#71 // "<init>":(Ljava/lang/String;I)V
#54 = NameAndType #26:#27 // name:Ljava/lang/String;
#55 = Class #72 // java/lang/System
#56 = NameAndType #73:#74 // out:Ljava/io/PrintStream;
#57 = Class #75 // java/io/PrintStream
#58 = NameAndType #76:#39 // println:(Ljava/lang/String;)V
#59 = NameAndType #22:#23 // ONE:LTestEnum;
#60 = NameAndType #24:#23 // TWO:LTestEnum;
#61 = NameAndType #25:#23 // TREE:LTestEnum;
#62 = NameAndType #40:#41 // show:()V
#63 = Utf8 one
#64 = NameAndType #36:#37 // "<init>":(Ljava/lang/String;ILjava/lang/String;)V
#65 = Utf8 two
#66 = Utf8 tree
#67 = Utf8 java/lang/Enum
#68 = Utf8 clone
#69 = Utf8 ()Ljava/lang/Object;
#70 = Utf8 (Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/Enum;
#71 = Utf8 (Ljava/lang/String;I)V
#72 = Utf8 java/lang/System
#73 = Utf8 out
#74 = Utf8 Ljava/io/PrintStream;
#75 = Utf8 java/io/PrintStream
#76 = Utf8 println
{
public static final TestEnum ONE;
descriptor: LTestEnum;
flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL, ACC_ENUM
public static final TestEnum TWO;
descriptor: LTestEnum;
flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL, ACC_ENUM
public static final TestEnum TREE;
descriptor: LTestEnum;
flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL, ACC_ENUM
public static TestEnum[] values();
descriptor: ()[LTestEnum;
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=1, locals=0, args_size=0
0: getstatic #1 // Field $VALUES:[LTestEnum;
3: invokevirtual #2 // Method "[LTestEnum;".clone:()Ljava/lang/Object;
6: checkcast #3 // class "[LTestEnum;"
9: areturn
LineNumberTable:
line 1: 0
public static TestEnum valueOf(java.lang.String);
descriptor: (Ljava/lang/String;)LTestEnum;
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=1, args_size=1
0: ldc #4 // class TestEnum
2: aload_0
3: invokestatic #5 // Method java/lang/Enum.valueOf:(Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/Enum;
6: checkcast #4 // class TestEnum
9: areturn
LineNumberTable:
line 1: 0
public void show();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=2, locals=1, args_size=1
0: getstatic #8 // Field java/lang/System.out:Ljava/io/PrintStream;
3: aload_0
4: getfield #7 // Field name:Ljava/lang/String;
7: invokevirtual #9 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
10: return
LineNumberTable:
line 8: 0
line 9: 10
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=1, locals=4, args_size=1
0: getstatic #10 // Field ONE:LTestEnum;
3: astore_1
4: getstatic #11 // Field TWO:LTestEnum;
7: astore_2
8: getstatic #12 // Field TREE:LTestEnum;
11: astore_3
12: aload_1
13: invokevirtual #13 // Method show:()V
16: aload_2
17: invokevirtual #13 // Method show:()V
20: aload_3
21: invokevirtual #13 // Method show:()V
24: return
LineNumberTable:
line 11: 0
line 12: 4
line 13: 8
line 14: 12
line 15: 16
line 16: 20
line 17: 24
static {};
descriptor: ()V
flags: ACC_STATIC
Code:
stack=5, locals=0, args_size=0
0: new #4 // class TestEnum
3: dup
4: ldc #14 // String ONE
6: iconst_0
7: ldc #15 // String one
9: invokespecial #16 // Method "<init>":(Ljava/lang/String;ILjava/lang/String;)V
12: putstatic #10 // Field ONE:LTestEnum;
15: new #4 // class TestEnum
18: dup
19: ldc #17 // String TWO
21: iconst_1
22: ldc #18 // String two
24: invokespecial #16 // Method "<init>":(Ljava/lang/String;ILjava/lang/String;)V
27: putstatic #11 // Field TWO:LTestEnum;
30: new #4 // class TestEnum
33: dup
34: ldc #19 // String TREE
36: iconst_2
37: ldc #20 // String tree
39: invokespecial #16 // Method "<init>":(Ljava/lang/String;ILjava/lang/String;)V
42: putstatic #12 // Field TREE:LTestEnum;
45: iconst_3
46: anewarray #4 // class TestEnum
49: dup
50: iconst_0
51: getstatic #10 // Field ONE:LTestEnum;
54: aastore
55: dup
56: iconst_1
57: getstatic #11 // Field TWO:LTestEnum;
60: aastore
61: dup
62: iconst_2
63: getstatic #12 // Field TREE:LTestEnum;
66: aastore
67: putstatic #1 // Field $VALUES:[LTestEnum;
70: return
LineNumberTable:
line 2: 0
line 1: 45
}
Signature: #45 // Ljava/lang/Enum<LTestEnum;>;
SourceFile: "TestEnum.java"