java Enum
2015-07-30 00:12
519 查看
在项目中经常使用Enum,我们知道Enum类型只能使用已经定义好的Enum类型,不能对Enum进行实例化。但是从来没有考虑过java是如何实现这个类型的。今天同事问了这个问题,借此机会正好看看。
首先定义Enum类型,
使用javap查看生成的class文件,
生成的内容如下所示,
可以看到jvm将对应的Enum类型编译成了同名的类,并且该类继承了java.lang.Enum。然后定义了两个静态的属性,属性名分别为enum1,enum2,它们的类型都是类EnumTest。这两个属性正好对应了枚举类型中定义的两个枚举值,它们的初始化在static静态块中。由于它们是static和public的,所以不需要实例化,直接通过EnumTest就可以直接调用,这与Enum的使用方式不谋而合。
来看看这两个属性的初始化过程,以enum1作为例子,
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)可以推断出:
那么为什么这个四个参数的构造函数在上面的反编译的内容中没有看到呢?原因就是该构造函数是私有的。为了验证这个结论我们用一个简单的类来验证这个结论,
javap之后,
可以看到只有公共的构造函数被打印出来了,而私有的两个参数的构造函数没有被打印出来。这也就解释了为什么Enum类型不能被实例化。
那么还有什么和普通的类不同呢?仔细看可以发现Enum编译出的类的flags为ACC_PUBLIC, ACC_FINAL, ACC_SUPER, ACC_ENUM,明显的比普通类多了一个ACC_ENUM。那么这个参数表明了什么呢?参看java虚拟机规范后可以看到它的意义,
然后再仔细看还能发现什么呢?似乎我们编写的Enum中没有values和valueOf这两个方法,而编译后的类中却有。可见这两个类是jvm在编译的时候自动加上的。来看看valueOf是怎么实现的。。
主要看invokestatic指令,发现其调用了父类的valueOf方法,
大概就是根据类名和枚举值的名字从Map中获取对应枚举值。
调用values方法,也就是上面定义的方法,该方法从私有变量ENUM$VALUES中将定义好的几个枚举类型取出组成数组返回。而私有变量ENUM$VALUES的初始化实在静态初始块中进行的,当两个静态属性enum1和enum2被初始化后,放置到了ENUM$VALUES中。。。
首先定义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中。。。
相关文章推荐
- spring-ioc
- java基础语法要点<二>(基于1.8)
- Java Technical Notes
- 学习 java netty (三) -- Channel
- Java注解
- java中的并发:同步
- 9个基于Java的搜索引擎框架
- java学习——处理json
- java Unicode 转为中文
- EJB3.0和Spring比较
- java 日期操作,Date、Calendar 操作
- java 生成sqlite3 DB文件
- java中的并发:同步
- 9个基于Java的搜索引擎框架
- JAVA IO
- 学习笔记——回顾struts2文件上传
- myeclipse汉化版中安装java反编译工具jadClipse详解
- Java并发编程:Callable、Future和FutureTask
- Java关键字final、static使用总结
- Java学习--(四)新的数据类型:类(class);field,method