这是一个枚举:
enum Foo { X }
我使用以下命令编译和反汇编(在Java 8u60上):
javac Foo.java && javap -c -p Foo
这是我得到的输出:
final class Foo extends java.lang.Enum<Foo> { public static final Foo X; private static final Foo[] $VALUES; public static Foo[] values(); Code: 0: getstatic #1 // Field $VALUES:[LFoo; 3: invokevirtual #2 // Method "[LFoo;".clone:()Ljava/lang/Object; 6: checkcast #3 // class "[LFoo;" 9: areturn public static Foo valueOf(java.lang.String); Code: 0: ldc #4 // class Foo 2: aload_0 3: invokestatic #5 // Method java/lang/Enum.valueOf:(Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/Enum; 6: checkcast #4 // class Foo 9: areturn private Foo(); // <--- here Code: 0: aload_0 1: aload_1 2: iload_2 3: invokespecial #6 // Method java/lang/Enum."<init>":(Ljava/lang/String;I)V 6: return static {}; Code: 0: new #4 // class Foo 3: dup 4: ldc #7 // String X 6: iconst_0 7: invokespecial #8 // Method "<init>":(Ljava/lang/String;I)V 10: putstatic #9 // Field X:LFoo; 13: iconst_1 14: anewarray #4 // class Foo 17: dup 18: iconst_0 19: getstatic #9 // Field X:LFoo; 22: aastore 23: putstatic #1 // Field $VALUES:[LFoo; 26: return }
我的困惑是用于实例化每个枚举常量的私有构造函数.反汇编显示它不带参数(私有Foo();),但肯定会带参数.例如,您可以看到读取传递的枚举常量名称和序号以及this指针的加载指令,并将它们传递给the superclass constructor,这需要它们.静态初始化程序块中的代码还显示它在调用构造函数之前将这些参数压入堆栈.
现在我假设这只是javap中一个不起眼的错误,但是当我使用Eclipse的编译器编译完全相同的枚举并使用javap反汇编时,构造函数完全相同,除了参数显示:
final class Foo extends java.lang.Enum<Foo> { public static final Foo X; private static final Foo[] ENUM$VALUES; static {}; Code: 0: new #1 // class Foo 3: dup 4: ldc #12 // String X 6: iconst_0 7: invokespecial #13 // Method "<init>":(Ljava/lang/String;I)V 10: putstatic #17 // Field X:LFoo; 13: iconst_1 14: anewarray #1 // class Foo 17: dup 18: iconst_0 19: getstatic #17 // Field X:LFoo; 22: aastore 23: putstatic #19 // Field ENUM$VALUES:[LFoo; 26: return private Foo(java.lang.String,int); // <--- here Code: 0: aload_0 1: aload_1 2: iload_2 3: invokespecial #23 // Method java/lang/Enum."<init>":(Ljava/lang/String;I)V 6: return public static Foo[] values(); Code: 0: getstatic #19 // Field ENUM$VALUES:[LFoo; 3: dup 4: astore_0 5: iconst_0 6: aload_0 7: arraylength 8: dup 9: istore_1 10: anewarray #1 // class Foo 13: dup 14: astore_2 15: iconst_0 16: iload_1 17: invokestatic #27 // Method java/lang/System.arraycopy:(Ljava/lang/Object;ILjava/lang/Object;II)V 20: aload_2 21: areturn public static Foo valueOf(java.lang.String); Code: 0: ldc #1 // class Foo 2: aload_0 3: invokestatic #35 // Method java/lang/Enum.valueOf:(Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/Enum; 6: checkcast #1 // class Foo 9: areturn }
我的问题是:javac编译的枚举和Eclipse编译的枚举之间有什么不同,导致javap不显示javac编译的枚举的构造函数参数?这是一个错误(在javap,javac或Eclipse中)?
解决方法
1.5中引入泛型.其他信息被引入到类文件格式method signature中.
“方法描述符”用于描述类型擦除之后的方法,“方法签名”另外包含通用类型信息.
现在javap打印出方法签名(包含更多信息),并且当设置-v标志时,它还会打印描述符.
这表明javac生成的enum类的构造函数也有一个参数类型为String和int的方法描述符.
现在也很清楚为什么Elipse和javac都生成了代码.两者都使用参数String和int调用私有构造函数.
还有什么需要解释的:为什么javac会创建一个与描述符完全不同的签名 – 不涉及泛型?
无论如何,javac关于枚举构造函数的行为导致了other troubles,javac的错误报告是filed:
There is no need for an enum declaration’s constructor to have a
Signature attribute storing a method signature if 1) the constructor
isn’t generic and 2) its formal parameter types are neither
parameterized types nor type variables. It’s a bug if javac expects a
Signature attribute for the constructor written above.