您的位置:首页 > 编程语言 > Java开发

java Enum

2015-07-30 00:12 519 查看
在项目中经常使用Enum,我们知道Enum类型只能使用已经定义好的Enum类型,不能对Enum进行实例化。但是从来没有考虑过java是如何实现这个类型的。今天同事问了这个问题,借此机会正好看看。

首先定义Enum类型,

public enum EnumTest {
enum1(1, 1), enum2(2, 2);
private int i;
private int j;
EnumTest(int i, int j) {
this.i = i;
this.j = j;
}
}


使用javap查看生成的class文件,

javap -verbose EnumTest.class


生成的内容如下所示,

Classfile /C:/Users/Administrator/workspace/Enum/bin/EnumTest.class
Last modified 2015-7-29; size 1015 bytes
MD5 checksum 17047593684833e83d2e404dd5de36df
Compiled from "EnumTest.java"
public final class EnumTest extends java.lang.Enum<EnumTest>
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_FINAL, ACC_SUPER, ACC_ENUM
Constant pool:
...... // 省略此处的常量池
{
public static final EnumTest enum1;
descriptor: LEnumTest;
flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL, ACC_ENUM

public static final EnumTest enum2;
descriptor: LEnumTest;
flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL, ACC_ENUM

static {};
descriptor: ()V
flags: ACC_STATIC
Code:
stack=6, locals=0, args_size=0
0: new           #1                  // class EnumTest
3: dup
4: ldc           #16                 // String enum1
6: iconst_0
7: iconst_1
8: iconst_1
9: invokespecial #17                 // Method "<init>":(Ljava/lang/String;III)V
12: putstatic     #21                 // Field enum1:LEnumTest;
15: new           #1                  // class EnumTest
18: dup
19: ldc           #23                 // String enum2
21: iconst_1
22: iconst_2
23: iconst_2
24: invokespecial #17                 // Method "<init>":(Ljava/lang/String;III)V
27: putstatic     #24                 // Field enum2:LEnumTest;
30: iconst_2
31: anewarray     #1                  // class EnumTest
34: dup
35: iconst_0
36: getstatic     #21                 // Field enum1:LEnumTest;
39: aastore
40: dup
41: iconst_1
42: getstatic     #24                 // Field enum2:LEnumTest;
45: aastore
46: putstatic     #26                 // Field ENUM$VALUES:[LEnumTest;
49: return
LineNumberTable:
line 3: 0
line 2: 30
LocalVariableTable:
Start  Length  Slot  Name   Signature

public static EnumTest[] values();
descriptor: ()[LEnumTest;
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=5, locals=3, args_size=0
0: getstatic     #26                 // Field ENUM$VALUES:[LEnumTest;
3: dup
4: astore_0
5: iconst_0
6: aload_0
7: arraylength
8: dup
9: istore_1
10: anewarray     #1                  // class EnumTest
13: dup
14: astore_2
15: iconst_0
16: iload_1
17: invokestatic  #40                 // Method java/lang/System.arraycopy:(Ljava/lang/Object;ILjava/lang/Object;II)V
20: aload_2
21: areturn
LineNumberTable:
line 1: 0
LocalVariableTable:
Start  Length  Slot  Name   Signature

public static EnumTest valueOf(java.lang.String);
descriptor: (Ljava/lang/String;)LEnumTest;
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=1, args_size=1
0: ldc           #1                  // class EnumTest
2: aload_0
3: invokestatic  #48                 // Method java/lang/Enum.valueOf:(Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/Enum;
6: checkcast     #1                  // class EnumTest
9: areturn
LineNumberTable:
line 1: 0
LocalVariableTable:
Start  Length  Slot  Name   Signature
}
SourceFile: "EnumTest.java"
Signature: #54                          // Ljava/lang/Enum<LEnumTest;>;


可以看到jvm将对应的Enum类型编译成了同名的类,并且该类继承了java.lang.Enum。然后定义了两个静态的属性,属性名分别为enum1,enum2,它们的类型都是类EnumTest。这两个属性正好对应了枚举类型中定义的两个枚举值,它们的初始化在static静态块中。由于它们是static和public的,所以不需要实例化,直接通过EnumTest就可以直接调用,这与Enum的使用方式不谋而合。

来看看这两个属性的初始化过程,以enum1作为例子,

0: new           #1                  // class EnumTest
3: dup
4: ldc           #16                 // String enum1
6: iconst_0
7: iconst_1
8: iconst_1
9: invokespecial #17                 // Method "<init>":(Ljava/lang/String;III)V
12: putstatic     #21                 // Field enum1:LEnumTest;


1、调用new实例化一个EnumTest对象

2、dup将该对象指针压到操作数栈中

3、ldc将字符串“enum1”压到操作数栈中

4、iconst_0将整数0压到操作数栈中

5、iconst_1将整数1压到操作数栈中

6、iconst_1将整数1压到操作数栈中

7、invokespecial调用初始化方法初始化对应的属性。可以看到构造函数的参数类型为(String, int, int, int),根据其父类的构造函数的参数类型为(String, int)可以推断出:

前两个参数为枚举值的名字和枚举值对应的整数值,第三个参数和第四个参数为枚举中自定义的两个属性


那么为什么这个四个参数的构造函数在上面的反编译的内容中没有看到呢?原因就是该构造函数是私有的。为了验证这个结论我们用一个简单的类来验证这个结论,

public class ClassTest {
public ClassTest(int i) {

}

private ClassTest(int i, int j) {

}
}


javap之后,

Classfile /C:/Users/Administrator/workspace/Enum/bin/ClassTest.class
Last modified 2015-7-29; size 378 bytes
MD5 checksum 55d836f2782757c8dd94fd773c77e702
Compiled from "ClassTest.java"
public class ClassTest
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
...... // 省略常量池
{
public ClassTest(int);
descriptor: (I)V
flags: ACC_PUBLIC
Code:
stack=1, locals=2, args_size=2
0: aload_0
1: invokespecial #8                  // Method java/lang/Object."<init>":()V
4: return
LineNumberTable:
line 3: 0
line 5: 4
LocalVariableTable:
Start  Length  Slot  Name   Signature
0       5     0  this   LClassTest;
0       5     1     i   I
}
SourceFile: "ClassTest.java"


可以看到只有公共的构造函数被打印出来了,而私有的两个参数的构造函数没有被打印出来。这也就解释了为什么Enum类型不能被实例化。

那么还有什么和普通的类不同呢?仔细看可以发现Enum编译出的类的flags为ACC_PUBLIC, ACC_FINAL, ACC_SUPER, ACC_ENUM,明显的比普通类多了一个ACC_ENUM。那么这个参数表明了什么呢?参看java虚拟机规范后可以看到它的意义,



然后再仔细看还能发现什么呢?似乎我们编写的Enum中没有values和valueOf这两个方法,而编译后的类中却有。可见这两个类是jvm在编译的时候自动加上的。来看看valueOf是怎么实现的。。

public class EnumUseTest {
public static void main(String[] args) {
EnumTest e = EnumTest.valueOf("enum1");
System.out.println(e);
}
}


0: ldc           #1                  // class EnumTest
2: aload_0
3: invokestatic  #48                 // Method java/lang/Enum.valueOf:(Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/Enum;
6: checkcast     #1                  // class EnumTest
9: areturn


主要看invokestatic指令,发现其调用了父类的valueOf方法,

public static <T extends Enum<T>> T valueOf(Class<T> 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);
}


Map<String, T> enumConstantDirectory() {
if (enumConstantDirectory == null) {
T[] universe = getEnumConstantsShared();
if (universe == null)
throw new IllegalArgumentException(
getName() + " is not an enum type");
Map<String, T> m = new HashMap<>(2 * universe.length);
for (T constant : universe)
m.put(((Enum<?>)constant).name(), constant);
enumConstantDirectory = m;
}
return enumConstantDirectory;
}


大概就是根据类名和枚举值的名字从Map中获取对应枚举值。

T[] getEnumConstantsShared() {
if (enumConstants == null) {
if (!isEnum()) return null;
try {
final Method values = getMethod("values");  // 这里获取values方法
java.security.AccessController.doPrivileged(
new java.security.PrivilegedAction<Void>() {
public Void run() {
values.setAccessible(true);
return null;
}
});
@SuppressWarnings("unchecked")
T[] temporaryConstants = (T[])values.invoke(null); // 调用values方法
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;
}


调用values方法,也就是上面定义的方法,该方法从私有变量ENUM$VALUES中将定义好的几个枚举类型取出组成数组返回。而私有变量ENUM$VALUES的初始化实在静态初始块中进行的,当两个静态属性enum1和enum2被初始化后,放置到了ENUM$VALUES中。。。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: