从字节码指令分析Java枚举类的运行过程。
目录
一、创建的枚举类
public enum ResponseCodeEnum {
SUCCESS("0000", "success"),
BUSINESS_EXCEPTION("5000", "wrong");
private String code;
private String message;
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
ResponseCodeEnum(String code, String message) {
this.code = code;
this.message = message;
}
}
二、反编译字节码
1)getCode()方法分析
字节码各指令含义在这里不再阐述,主要讲一下字节码运行之后会产生什么效果。
了解过JVM内存模型的都知道,JVM中每一个方法都会存在一个方法栈。而字节码执行中,就会存在一个操作数栈,一个局部表量表,对应图片中stack就是操作数栈,局部变量可以在LocalVariableTable中看到;其中,Slot就是字节码中操作的一个下标。
在大概了解了执行中存在哪些资源后,就可以开始分析了。
首先public java.lang.String getCode()就是定义的方法声明,下面的descriptor也是描述方法的;()表示这个方法,并且没有参数,Ljava/lang/String,其中L表示这个一个引用类,后面表示这个引用类是什么类型,意思就是这个方法有返回值,返回值类型为字符串类型。
从Code:下面开始依次分析每一行字节码含义。
这里的stack表示操作栈的深度,为1,locals是局部变量表的变量个数,args_size表示参数个数,这里getCode()没有参数,但是显示为1,是因为每个实例方法都会有一个隐藏参数this。
aload_0表示将Slot0这个变量压入操作栈栈顶,即将this压入栈
getfield很好理解,就是获取this对应的code的这个变量的值,并且会把此时位于栈顶的this弹出栈。
areturn就是返回了。局部变量表和栈都会清零。
2)setCode()方法分析
和getCode()相同的地方就不再阐述,这里说一下descriptor,(Ljava/lang/String;)表示这个方法有一个参数,参数为String类型,V表示该方法没有返回值,为Void
aload_0,将这个对象this压入栈
aload_1,将code这个参数的值压入栈,此时,code在栈顶
putfield,将栈顶的code的值赋值给栈顶下一个this对象对应的属性,此时,this的code成员属性的值就是参数code的值了
return。局部变量表和栈都会清零。
三、枚举常量给枚举类成员变量赋值过程分析
通过上面getCode()和setCode(),可以大致熟悉一下字节码指令做的事了,下面就分析,枚举常量是如何通过构造函数给枚举类成员变量赋值的。
所有的枚举类都继承自Enum这个公共基类,在这个公共基类中,有一个构造方法,如下:
Enum(String name, int ordinal),其中name就是我们平常写在枚举类中的常量,在这个例子中就是SUCCESS和FAIL,ordinal则是枚举常量的顺序,这里SUCCESS就是0,FAIL就是1。
构造函数字节码如下:
构造函数也比较简单,主要就是前面4行,前面4行就是在执行公共基类的构造函数。aload_0和aload_1会在后面讲到。
第三行invokespecial执行的方法,通过注释可以看到那个方法是没有返回值的,参数是String和int,和公共基类的方法一致。
接下来就是枚举常量的执行字节码:
new,运行构造函数,创建一个枚举类实例,并压入栈顶
dup,复制栈顶数据,并压入栈,此时操作数栈有两个值,都是new的实例
ldc,从常量池中取出SUCCESS并压入栈
iconst_0,将基本类型int的0压入栈
ldc,从常量池中取出SUCCESS中的code的值(这里是0000)并压入栈
ldc,从常量池中取出SUCCESS中的success的值(这里是success)并压入栈
invokespecial,执行构造函数,这里通过注释,可以分析出这个方法是 method(String, int, String, String),公共基类Enum的构造函数和自定义的枚举类的构造函数的合并。
putstatic,为枚举类的SUCCESS常量赋值。
一个枚举类大致就会经历这几个过程,从字节码我们可以看到,枚举类的常量都是static的,虽然我们没有显示的指定。