JDK 之 Enum 源码阅读笔记
Contents
Enum 类声明
public abstract class Enum<E extends Enum<E>> implements Comparable<E>, Serializable
可以看到,它是一个抽象类,实现的 Comparable 和 Serializable 接口
Enum 类的属性
- private final String name; 所代表的名称(即 enum 的名称,也即是枚举字段的变量的字面量所表示的字符串,这个并不能改变的,因为它是 final 的)
- private final int ordinal; 所代表的顺序(即 enum 声明的顺序,从0开始,这个并不能改变的,因为它是 final 的)
Enum 类的方法
protected Enum(String name, int ordinal); 构造函数
public final String name(); 获取它的 name
public final int ordinal(); 获取它的 ordinal
public static
> T valueOf(Class enumType, String name); 返回指定 name 所代表的枚举类型
枚举类
package org.agoncal.sample.jmh;
/**
* Created by emacsist on 2017/7/3.
*/
public enum Hello {
HELLO,
WORLD
}
枚举类的字节码及分析
[15:27:28] emacsist:classes $ javap -verbose org.agoncal.sample.jmh.Hello
Classfile /Users/emacsist/Documents/idea/logging/target/classes/org/agoncal/sample/jmh/Hello.class
Last modified 2017-7-3; size 981 bytes
MD5 checksum 2ee3f1a21018896c715ed7e8db96fad6
Compiled from "Hello.java"
public final class org.agoncal.sample.jmh.Hello extends java.lang.Enum<org.agoncal.sample.jmh.Hello>
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_FINAL, ACC_SUPER, ACC_ENUM
Constant pool:
#1 = Fieldref #4.#36 // org/agoncal/sample/jmh/Hello.$VALUES:[Lorg/agoncal/sample/jmh/Hello;
#2 = Methodref #37.#38 // "[Lorg/agoncal/sample/jmh/Hello;".clone:()Ljava/lang/Object;
#3 = Class #17 // "[Lorg/agoncal/sample/jmh/Hello;"
#4 = Class #39 // org/agoncal/sample/jmh/Hello
#5 = Methodref #12.#40 // java/lang/Enum.valueOf:(Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/Enum;
#6 = Methodref #12.#41 // java/lang/Enum."<init>":(Ljava/lang/String;I)V
#7 = String #13 // HELLO
#8 = Methodref #4.#41 // org/agoncal/sample/jmh/Hello."<init>":(Ljava/lang/String;I)V
#9 = Fieldref #4.#42 // org/agoncal/sample/jmh/Hello.HELLO:Lorg/agoncal/sample/jmh/Hello;
#10 = String #15 // WORLD
#11 = Fieldref #4.#43 // org/agoncal/sample/jmh/Hello.WORLD:Lorg/agoncal/sample/jmh/Hello;
#12 = Class #44 // java/lang/Enum
#13 = Utf8 HELLO
#14 = Utf8 Lorg/agoncal/sample/jmh/Hello;
#15 = Utf8 WORLD
#16 = Utf8 $VALUES
#17 = Utf8 [Lorg/agoncal/sample/jmh/Hello;
#18 = Utf8 values
#19 = Utf8 ()[Lorg/agoncal/sample/jmh/Hello;
#20 = Utf8 Code
#21 = Utf8 LineNumberTable
#22 = Utf8 valueOf
#23 = Utf8 (Ljava/lang/String;)Lorg/agoncal/sample/jmh/Hello;
#24 = Utf8 LocalVariableTable
#25 = Utf8 name
#26 = Utf8 Ljava/lang/String;
#27 = Utf8 <init>
#28 = Utf8 (Ljava/lang/String;I)V
#29 = Utf8 this
#30 = Utf8 Signature
#31 = Utf8 ()V
#32 = Utf8 <clinit>
#33 = Utf8 Ljava/lang/Enum<Lorg/agoncal/sample/jmh/Hello;>;
#34 = Utf8 SourceFile
#35 = Utf8 Hello.java
#36 = NameAndType #16:#17 // $VALUES:[Lorg/agoncal/sample/jmh/Hello;
#37 = Class #17 // "[Lorg/agoncal/sample/jmh/Hello;"
#38 = NameAndType #45:#46 // clone:()Ljava/lang/Object;
#39 = Utf8 org/agoncal/sample/jmh/Hello
#40 = NameAndType #22:#47 // valueOf:(Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/Enum;
#41 = NameAndType #27:#28 // "<init>":(Ljava/lang/String;I)V
#42 = NameAndType #13:#14 // HELLO:Lorg/agoncal/sample/jmh/Hello;
#43 = NameAndType #15:#14 // WORLD:Lorg/agoncal/sample/jmh/Hello;
#44 = Utf8 java/lang/Enum
#45 = Utf8 clone
#46 = Utf8 ()Ljava/lang/Object;
#47 = Utf8 (Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/Enum;
{
public static final org.agoncal.sample.jmh.Hello HELLO;
descriptor: Lorg/agoncal/sample/jmh/Hello;
flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL, ACC_ENUM
public static final org.agoncal.sample.jmh.Hello WORLD;
descriptor: Lorg/agoncal/sample/jmh/Hello;
flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL, ACC_ENUM
public static org.agoncal.sample.jmh.Hello[] values();
descriptor: ()[Lorg/agoncal/sample/jmh/Hello;
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=1, locals=0, args_size=0
0: getstatic #1 // Field $VALUES:[Lorg/agoncal/sample/jmh/Hello;
3: invokevirtual #2 // Method "[Lorg/agoncal/sample/jmh/Hello;".clone:()Ljava/lang/Object;
6: checkcast #3 // class "[Lorg/agoncal/sample/jmh/Hello;"
9: areturn
LineNumberTable:
line 6: 0
public static org.agoncal.sample.jmh.Hello valueOf(java.lang.String);
descriptor: (Ljava/lang/String;)Lorg/agoncal/sample/jmh/Hello;
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=1, args_size=1
0: ldc #4 // class org/agoncal/sample/jmh/Hello
2: aload_0
3: invokestatic #5 // Method java/lang/Enum.valueOf:(Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/Enum;
6: checkcast #4 // class org/agoncal/sample/jmh/Hello
9: areturn
LineNumberTable:
line 6: 0
LocalVariableTable:
Start Length Slot Name Signature
0 10 0 name Ljava/lang/String;
static {};
descriptor: ()V
flags: ACC_STATIC
Code:
stack=4, locals=0, args_size=0
0: new #4 // class org/agoncal/sample/jmh/Hello
3: dup
4: ldc #7 // String HELLO
6: iconst_0
7: invokespecial #8 // Method "<init>":(Ljava/lang/String;I)V
10: putstatic #9 // Field HELLO:Lorg/agoncal/sample/jmh/Hello;
13: new #4 // class org/agoncal/sample/jmh/Hello
16: dup
17: ldc #10 // String WORLD
19: iconst_1
20: invokespecial #8 // Method "<init>":(Ljava/lang/String;I)V
23: putstatic #11 // Field WORLD:Lorg/agoncal/sample/jmh/Hello;
26: iconst_2
27: anewarray #4 // class org/agoncal/sample/jmh/Hello
30: dup
31: iconst_0
32: getstatic #9 // Field HELLO:Lorg/agoncal/sample/jmh/Hello;
35: aastore
36: dup
37: iconst_1
38: getstatic #11 // Field WORLD:Lorg/agoncal/sample/jmh/Hello;
41: aastore
42: putstatic #1 // Field $VALUES:[Lorg/agoncal/sample/jmh/Hello;
45: return
LineNumberTable:
line 7: 0
line 8: 13
line 6: 26
}
Signature: #33 // Ljava/lang/Enum<Lorg/agoncal/sample/jmh/Hello;>;
SourceFile: "Hello.java"
可看到,编译器为我们自动将枚举字段变成:
public static final org.agoncal.sample.jmh.Hello HELLO;
public static final org.agoncal.sample.jmh.Hello WORLD;
它还为我们生成了以下这些方法:
// 返回该枚举类的所有 value
public static org.agoncal.sample.jmh.Hello[] values();
// 返回 String 表示的枚举对象
public static org.agoncal.sample.jmh.Hello valueOf(java.lang.String);
还有一个静态语句块(它的作用,就是为枚举类生成相应的默认的构造函数代码)
static {};
4: ldc #7 // String HELLO
6: iconst_0
17: ldc #10 // String WORLD
19: iconst_1
可以看到,编译器为我们自动生成的代码中,默认的 name 就是与字段的名称相同,ordinal 则按声明的顺序,从0开始递增。
构造完成后,分别将构造完成的对象,放到编译器自动生成的字段数组中:
$VALUES
自动生成的 values() 方法返回的就这个字段的所代表的数组了。
switch 与 枚举类
如果枚举类,用在 switch 语句上,Java编译器还会生成一个特殊的内部类:
public static final void s(Hello hello) {
switch (hello) {
case WORLD:
System.out.println("world");
case HELLO:
System.out.println("hello");
break;
default:
System.out.println("default");
}
}
比如这段代码,Java编译器还会额外生成一个内部类:
SourceFile: "Test.java"
EnclosingMethod: #21.#0 // org.agoncal.sample.jmh.Test
InnerClasses:
static #7; //class org/agoncal/sample/jmh/Test$1
Test$1的字节码如下:
{
static final int[] $SwitchMap$org$agoncal$sample$jmh$Hello;
descriptor: [I
flags: ACC_STATIC, ACC_FINAL, ACC_SYNTHETIC
static {};
descriptor: ()V
flags: ACC_STATIC
Code:
stack=3, locals=1, args_size=0
0: invokestatic #1 // Method org/agoncal/sample/jmh/Hello.values:()[Lorg/agoncal/sample/jmh/Hello;
3: arraylength
4: newarray int
6: putstatic #2 // Field $SwitchMap$org$agoncal$sample$jmh$Hello:[I
9: getstatic #2 // Field $SwitchMap$org$agoncal$sample$jmh$Hello:[I
12: getstatic #3 // Field org/agoncal/sample/jmh/Hello.WORLD:Lorg/agoncal/sample/jmh/Hello;
15: invokevirtual #4 // Method org/agoncal/sample/jmh/Hello.ordinal:()I
18: iconst_1
19: iastore
20: goto 24
23: astore_0
24: getstatic #2 // Field $SwitchMap$org$agoncal$sample$jmh$Hello:[I
27: getstatic #6 // Field org/agoncal/sample/jmh/Hello.HELLO:Lorg/agoncal/sample/jmh/Hello;
30: invokevirtual #4 // Method org/agoncal/sample/jmh/Hello.ordinal:()I
33: iconst_2
34: iastore
35: goto 39
38: astore_0
39: return
Exception table:
from to target type
9 20 23 Class java/lang/NoSuchFieldError
24 35 38 Class java/lang/NoSuchFieldError
LineNumberTable:
line 11: 0
LocalVariableTable:
Start Length Slot Name Signature
StackMapTable: number_of_entries = 4
frame_type = 87 /* same_locals_1_stack_item */
stack = [ class java/lang/NoSuchFieldError ]
frame_type = 0 /* same */
frame_type = 77 /* same_locals_1_stack_item */
stack = [ class java/lang/NoSuchFieldError ]
frame_type = 0 /* same */
}
可以看到,这个额外生成的类的作用:
- 声明了一个名为 static final int[] $SwitchMap$org$agoncal$sample$jmh$Hello 的 int[] 数组。
- 用静态块语句来为这个数组进行初始化
伪代码如下:
static final int[] $SwitchMap$org$agoncal$sample$jmh$Hello;
static {
$SwitchMap$org$agoncal$sample$jmh$Hello = new int[org.agoncal.sample.jmh.Hello.values().length];
try {
$SwitchMap$org$agoncal$sample$jmh$Hello[Hello.WORLD.ordinal()] = 1;
} catch (NoSuchFieldError var2) {
;
}
try {
$SwitchMap$org$agoncal$sample$jmh$Hello[Hello.HELLO.ordinal()] = 2;
} catch (NoSuchFieldError var1) {
;
}
}
注意,这个 SwitchMap 的索引是枚举类型的 ordinal 的值,然后它对应的值是从 1 开始的。
可以看到,上面的 swith 的代码生成的字节码如下:
8: lookupswitch { // 2
1: 36
2: 44
default: 55
}
1:36 中的 1 就表示通过 SwitchMap[ordinal] 来获取它的值,然后用相对应的 int 值来直接对比它们是否相等。
想覆盖 name, ordinal 的值?
如果我们在自定义的枚举类中,手动声明了 name, ordinal ,会有什么情况?
比如:
package org.agoncal.sample.jmh;
/**
* Created by emacsist on 2017/7/3.
*/
public enum Hello {
HELLO("hello", 1) ,
WORLD("world", 2);
private String name;
private int ordinal;
Hello(String name, int ordinal){
this.name = name;
this.ordinal = ordinal;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getOrdinal() {
return ordinal;
}
public void setOrdinal(int ordinal) {
this.ordinal = ordinal;
}
}
可以看到,Java编译器自动为我们生成的代码的字节码如下:
static {};
descriptor: ()V
flags: ACC_STATIC
Code:
stack=6, locals=0, args_size=0
0: new #4 // class org/agoncal/sample/jmh/Hello
3: dup
4: ldc #9 // String HELLO
6: iconst_0
7: ldc #10 // String hello
9: iconst_1
10: invokespecial #11 // Method "<init>":(Ljava/lang/String;ILjava/lang/String;I)V
13: putstatic #12 // Field HELLO:Lorg/agoncal/sample/jmh/Hello;
16: new #4 // class org/agoncal/sample/jmh/Hello
19: dup
20: ldc #13 // String WORLD
22: iconst_1
23: ldc #14 // String world
25: iconst_2
26: invokespecial #11 // Method "<init>":(Ljava/lang/String;ILjava/lang/String;I)V
29: putstatic #15 // Field WORLD:Lorg/agoncal/sample/jmh/Hello;
32: iconst_2
33: anewarray #4 // class org/agoncal/sample/jmh/Hello
36: dup
37: iconst_0
38: getstatic #12 // Field HELLO:Lorg/agoncal/sample/jmh/Hello;
41: aastore
42: dup
43: iconst_1
44: getstatic #15 // Field WORLD:Lorg/agoncal/sample/jmh/Hello;
47: aastore
48: putstatic #1 // Field $VALUES:[Lorg/agoncal/sample/jmh/Hello;
51: return
可以推测伪代码如下:
构造函数变为:
Hello(String, int, String, int){
}
Hello.HELLO = new Hello("HELLO", 0, "hello", 1);
Hello.WORLD = new Hello("WORLD", 1, "world", 2);
可以看到,虽然我们自定义了两个字段,即使它与 Enum 类中的相同,但Java编译器在为我们自动生成代码时,会一起保存的,这就确保了调用 Enum 的 name() 方法,返回的一定是枚举字段的字面量的值, ordinal() 方法,返回的是枚举声明的顺序的值了。