目录
jdk版本 | 1.8.0_102 |
新建一个枚举Sex
package com.aya;
public enum Sex {
MALE,FEMALE;
public void goToilet(){
System.out.println("去上厕所了");
}
}
执行方法
@Test
public void testSex(){
Sex male = Sex.MALE;
male.goToilet();
}
控制台输出: 去上厕所了
通过jdk自带的反编译工具 javap 进行反编译
执行 javap -p com/aya/Sex.class
public final class com.aya.Sex extends java.lang.Enum<com.aya.Sex> {
public static final com.aya.Sex MALE;
public static final com.aya.Sex FEMALE;
private static final com.aya.Sex[] $VALUES;
public static com.aya.Sex[] values();
public static com.aya.Sex valueOf(java.lang.String);
private com.aya.Sex();
public void goToilet();
static {};
}
得出结论: 枚举实际上就是一个”特殊的类”
既然是一个类,就可以自由定义静态方法,成员方法,实现接口等…
用命令将枚举反编译成java汇编代码
执行: javap -v -p com/aya/Sex.class
获得枚举字节码内容
本文重点解析,三个语句块做了什么
1. 构造函数
1. static 静态块
2. valuesOf
3. values
然后对自定义函数 goToilet ,进行一个简单的解析
从 字节码详情 摘抄构造函数
private com.aya.Sex();
descriptor: (Ljava/lang/String;I)V
flags: ACC_PRIVATE
Code:
stack=3, locals=3, args_size=3
0: aload_0
1: aload_1
2: iload_2
3: invokespecial #6 // Method java/lang/Enum."" :(Ljava/lang/String;I)V
6: return
LineNumberTable:
line 3: 0
LocalVariableTable:
Start Length Slot Name Signature
0 7 0 this Lcom/aya/Sex;
Signature: #34 // ()V
翻译为java代码
super(param1,param2)
从 字节码详情 摘抄静态块
static {};
descriptor: ()V
flags: ACC_STATIC
Code:
stack=4, locals=0, args_size=0
0: new #4 // class com/aya/Sex
3: dup
4: ldc #10 // String MALE
6: iconst_0
7: invokespecial #11 // Method "":(Ljava/lang/String;I)V
10: putstatic #12 // Field MALE:Lcom/aya/Sex;
13: new #4 // class com/aya/Sex
16: dup
17: ldc #13 // String FEMALE
19: iconst_1
20: invokespecial #11 // Method "":(Ljava/lang/String;I)V
23: putstatic #14 // Field FEMALE:Lcom/aya/Sex;
26: iconst_2
27: anewarray #4 // class com/aya/Sex
30: dup
31: iconst_0
32: getstatic #12 // Field MALE:Lcom/aya/Sex;
35: aastore
36: dup
37: iconst_1
38: getstatic #14 // Field FEMALE:Lcom/aya/Sex;
41: aastore
42: putstatic #1 // Field $VALUES:[Lcom/aya/Sex;
45: return
LineNumberTable:
line 4: 0
line 3: 26
将上面的字节码翻译为Java源码
String male= "MALE";//4
int maleNumber= 0;//6
Sex sexA = new Sex(male,maleNumber);//0-3 和 7
Sex.MALE = sexA; // 10
int maleNumberA= 0;//6
String female= "FEMALE";//17
int femaleNumber = 1;//19
Sex sexB = new Sex(female,femaleNumber);//13-16 和 20
Sex.FEMALE = sexB; // 23
int arrlength = 2;//26
Sex [] sexArray = new Sex[arrlength];//27-30
int numA = 0;//31
sexArray[numA]=Sex.MALE;//32-36
int numB = 1;//37
sexArray[numB]=Sex.FEMALE;//38-41
Sex.$VALUES = sexArray;//42
return;//45
接下来简化成我们平时写的代码:
Sex male = new Sex("MALE",0);
Sex female = new Sex("FEMALE",1);
Sex [] sexArray = new Sex[2];
sexArray[0]=male;
sexArray[1]=female;
Sex.$VALUES = sexArray;
静态块中,做了下面两件事
1. 依次为所有的字段赋值
2. 将所有枚举值依次加入到 $VALUES 数组中
从 字节码详情 摘抄出来values部分
public static com.aya.Sex[] values();
descriptor: ()[Lcom/aya/Sex;
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=1, locals=0, args_size=0
0: getstatic #1 // Field $VALUES:[Lcom/aya/Sex;
3: invokevirtual #2 // Method "[Lcom/aya/Sex;".clone:()Ljava/lang/Object;
6: checkcast #3 // class "[Lcom/aya/Sex;"
9: areturn
LineNumberTable:
line 3: 0
翻译为 java 代码
T [] arr = Sex.$VALUES;// 0
return (Sex[])arr.clone();//3-9
clone 一份数组返回.
所以如果你可以任意修改返回的数组,而不用担心对values的返回值有任何影响
先从 字节码详情 摘抄出来valueOf部分
public static com.aya.Sex valueOf(java.lang.String);
descriptor: (Ljava/lang/String;)Lcom/aya/Sex;
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=1, args_size=1
0: ldc #4 // class com/aya/Sex
2: aload_0
3: invokestatic #5 // Method java/lang/Enum.valueOf:(Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/Enum;
6: checkcast #4 // class com/aya/Sex
9: areturn
LineNumberTable:
line 3: 0
LocalVariableTable:
Start Length Slot Name Signature
0 10 0 name Ljava/lang/String;
翻译为java代码
Class clazz = com.aya.Sex.class;//0
// param1 是第一个参数
Enum var = Enum.valueOf(clazz,param1);//2-3
return (Sex)var;//6-9
事实上用的Enum的valueOf转换的. 接着跟进Enum.valueOf
public abstract class Enum<E extends Enum<E>>
implements Comparable<E>, Serializable {
//省略其他函数
public static > T valueOf(Class enumType,
String name) {
T result = enumType.enumConstantDirectory().get(name);
if (result != null)
return result;
if (name == null)
throw new NullPointerException("Name is null");
throw new IllegalArgumentException(
"No enum constant " + enumType.getCanonicalName() + "." + name);
}
}
从 字节码详情 摘抄出来goToilet部分
public void goToilet();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=2, locals=1, args_size=1
0: getstatic #7 // Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc #8 // String 去上厕所了
5: invokevirtual #9 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
8: return
LineNumberTable:
line 7: 0
line 8: 8
LocalVariableTable:
Start Length Slot Name Signature
0 9 0 this Lcom/aya/Sex;
翻译为源码:
PrintStream print = java.lang.System.out;
print.println("去上厕所了");
当然我们写源码通常也不会把输出语句写成两行.
但是虚拟机在字节码的时候,得一步一步来. 我们还原的时候,可以根据我们的习惯进行还原
public final class Class<T> implements java.io.Serializable,
GenericDeclaration,
Type,
AnnotatedElement {
Map enumConstantDirectory() {
if (enumConstantDirectory == null) {
T[] universe = getEnumConstantsShared();
if (universe == null)
throw new IllegalArgumentException(
getName() + " is not an enum type");
Map m = new HashMap<>(2 * universe.length);
for (T constant : universe)
m.put(((Enum>)constant).name(), constant);
enumConstantDirectory = m;
}
return enumConstantDirectory;
}
}
这个类是一个友元方法,只能同包调用,我们也调用不了
问题就在于 getEnumConstantsShared 做了什么。如何通过 class 获取具体对象数组?
public final class Class<T> implements java.io.Serializable,
GenericDeclaration,
Type,
AnnotatedElement {
T[] getEnumConstantsShared() {
if (enumConstants == null) {
if (!isEnum()) return null;
try {
final Method values = getMethod("values");
java.security.AccessController.doPrivileged(
new java.security.PrivilegedAction() {
public Void run() {
values.setAccessible(true);
return null;
}
});
@SuppressWarnings("unchecked")
T[] temporaryConstants = (T[])values.invoke(null);
enumConstants = temporaryConstants;
}
// These can happen when users concoct enum-like classes
// that don't comply with the enum spec.
catch (InvocationTargetException | NoSuchMethodException |
IllegalAccessException ex) { return null; }
}
return enumConstants;
}
}
还是Class类的一个友元方法
method.invoke(null) 执行静态方法
method.invoke(obj) 执行对象的方法
这里事实上是约定的一个规则. 枚举类必须要有values 静态方法.
如果没有的话,调用values和valueOf 都会报错
Classfile /E:/workspace/source-analysis/java-core/enum-analysis/target/classes/com/aya/Sex.class
Last modified 2018-7-5; size 1105 bytes
MD5 checksum 43e1d49d4d9f7023f32173510d1b38db
Compiled from "Sex.java"
public final class com.aya.Sex extends java.lang.Enum<com.aya.Sex>
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_FINAL, ACC_SUPER, ACC_ENUM
Constant pool:
#1 = Fieldref #4.#40 // com/aya/Sex.$VALUES:[Lcom/aya/Sex;
#2 = Methodref #41.#42 // "[Lcom/aya/Sex;".clone:()Ljava/lang/Object;
#3 = Class #20 // "[Lcom/aya/Sex;"
#4 = Class #43 // com/aya/Sex
#5 = Methodref #15.#44 // java/lang/Enum.valueOf:(Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/Enum;
#6 = Methodref #15.#45 // java/lang/Enum."":(Ljava/lang/String;I)V
#7 = Fieldref #46.#47 // java/lang/System.out:Ljava/io/PrintStream;
#8 = String #48 // 去上厕所了
#9 = Methodref #49.#50 // java/io/PrintStream.println:(Ljava/lang/String;)V
#10 = String #16 // MALE
#11 = Methodref #4.#45 // com/aya/Sex."":(Ljava/lang/String;I)V
#12 = Fieldref #4.#51 // com/aya/Sex.MALE:Lcom/aya/Sex;
#13 = String #18 // FEMALE
#14 = Fieldref #4.#52 // com/aya/Sex.FEMALE:Lcom/aya/Sex;
#15 = Class #53 // java/lang/Enum
#16 = Utf8 MALE
#17 = Utf8 Lcom/aya/Sex;
#18 = Utf8 FEMALE
#19 = Utf8 $VALUES
#20 = Utf8 [Lcom/aya/Sex;
#21 = Utf8 values
#22 = Utf8 ()[Lcom/aya/Sex;
#23 = Utf8 Code
#24 = Utf8 LineNumberTable
#25 = Utf8 valueOf
#26 = Utf8 (Ljava/lang/String;)Lcom/aya/Sex;
#27 = Utf8 LocalVariableTable
#28 = Utf8 name
#29 = Utf8 Ljava/lang/String;
#30 = Utf8 <init>
#31 = Utf8 (Ljava/lang/String;I)V
#32 = Utf8 this
#33 = Utf8 Signature
#34 = Utf8 ()V
#35 = Utf8 goToilet
#36 = Utf8 <clinit>
#37 = Utf8 Ljava/lang/Enum<Lcom/aya/Sex;>;
#38 = Utf8 SourceFile
#39 = Utf8 Sex.java
#40 = NameAndType #19:#20 // $VALUES:[Lcom/aya/Sex;
#41 = Class #20 // "[Lcom/aya/Sex;"
#42 = NameAndType #54:#55 // clone:()Ljava/lang/Object;
#43 = Utf8 com/aya/Sex
#44 = NameAndType #25:#56 // valueOf:(Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/Enum;
#45 = NameAndType #30:#31 // "":(Ljava/lang/String;I)V
#46 = Class #57 // java/lang/System
#47 = NameAndType #58:#59 // out:Ljava/io/PrintStream;
#48 = Utf8 去上厕所了
#49 = Class #60 // java/io/PrintStream
#50 = NameAndType #61:#62 // println:(Ljava/lang/String;)V
#51 = NameAndType #16:#17 // MALE:Lcom/aya/Sex;
#52 = NameAndType #18:#17 // FEMALE:Lcom/aya/Sex;
#53 = Utf8 java/lang/Enum
#54 = Utf8 clone
#55 = Utf8 ()Ljava/lang/Object;
#56 = Utf8 (Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/Enum;
#57 = Utf8 java/lang/System
#58 = Utf8 out
#59 = Utf8 Ljava/io/PrintStream;
#60 = Utf8 java/io/PrintStream
#61 = Utf8 println
#62 = Utf8 (Ljava/lang/String;)V
{
public static final com.aya.Sex MALE;
descriptor: Lcom/aya/Sex;
flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL, ACC_ENUM
public static final com.aya.Sex FEMALE;
descriptor: Lcom/aya/Sex;
flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL, ACC_ENUM
private static final com.aya.Sex[] $VALUES;
descriptor: [Lcom/aya/Sex;
flags: ACC_PRIVATE, ACC_STATIC, ACC_FINAL, ACC_SYNTHETIC
public static com.aya.Sex[] values();
descriptor: ()[Lcom/aya/Sex;
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=1, locals=0, args_size=0
0: getstatic #1 // Field $VALUES:[Lcom/aya/Sex;
3: invokevirtual #2 // Method "[Lcom/aya/Sex;".clone:()Ljava/lang/Object;
6: checkcast #3 // class "[Lcom/aya/Sex;"
9: areturn
LineNumberTable:
line 3: 0
public static com.aya.Sex valueOf(java.lang.String);
descriptor: (Ljava/lang/String;)Lcom/aya/Sex;
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=1, args_size=1
0: ldc #4 // class com/aya/Sex
2: aload_0
3: invokestatic #5 // Method java/lang/Enum.valueOf:(Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/Enum;
6: checkcast #4 // class com/aya/Sex
9: areturn
LineNumberTable:
line 3: 0
LocalVariableTable:
Start Length Slot Name Signature
0 10 0 name Ljava/lang/String;
private com.aya.Sex();
descriptor: (Ljava/lang/String;I)V
flags: ACC_PRIVATE
Code:
stack=3, locals=3, args_size=3
0: aload_0
1: aload_1
2: iload_2
3: invokespecial #6 // Method java/lang/Enum."":(Ljava/lang/String;I)V
6: return
LineNumberTable:
line 3: 0
LocalVariableTable:
Start Length Slot Name Signature
0 7 0 this Lcom/aya/Sex;
Signature: #34 // ()V
public void goToilet();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=2, locals=1, args_size=1
0: getstatic #7 // Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc #8 // String 去上厕所了
5: invokevirtual #9 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
8: return
LineNumberTable:
line 7: 0
line 8: 8
LocalVariableTable:
Start Length Slot Name Signature
0 9 0 this Lcom/aya/Sex;
static {};
descriptor: ()V
flags: ACC_STATIC
Code:
stack=4, locals=0, args_size=0
0: new #4 // class com/aya/Sex
3: dup
4: ldc #10 // String MALE
6: iconst_0
7: invokespecial #11 // Method "":(Ljava/lang/String;I)V
10: putstatic #12 // Field MALE:Lcom/aya/Sex;
13: new #4 // class com/aya/Sex
16: dup
17: ldc #13 // String FEMALE
19: iconst_1
20: invokespecial #11 // Method "":(Ljava/lang/String;I)V
23: putstatic #14 // Field FEMALE:Lcom/aya/Sex;
26: iconst_2
27: anewarray #4 // class com/aya/Sex
30: dup
31: iconst_0
32: getstatic #12 // Field MALE:Lcom/aya/Sex;
35: aastore
36: dup
37: iconst_1
38: getstatic #14 // Field FEMALE:Lcom/aya/Sex;
41: aastore
42: putstatic #1 // Field $VALUES:[Lcom/aya/Sex;
45: return
LineNumberTable:
line 4: 0
line 3: 26
}
Signature: #37 // Ljava/lang/Enum;
SourceFile: "Sex.java"
java 字节码指令集和功能描述: https://blog.csdn.net/sum_rain/article/details/39892219