存储在java类文件中的generics类型在哪里?
我很清楚generics类型在编译时会从Java代码中删除。 1.5+ JVM使用什么信息(属性?)来实现getGenericType
等?
它们存储在Signature
属性中; 有关字段类型签名的格式,请参阅更新的Java虚拟机规范的 4.8.8节以及第4.4.4节。
这是使用javap -verbose java.util.Map
的示例:
public interface java.util.Map SourceFile: "Map.java" Signature: length = 0x2 00 1E [other attributes omitted]
此处的Signature
属性指定(如果您将其视为big-endian,就像JVM类文件格式中的所有整数一样),常量池值#30(30 = 0x1E)。 那么让我们来看看:
const #30 = Asciz Ljava/lang/Object;;
在4.4.4中指定的语法上下文中阅读。 因此,这使用了两个类型参数, K extends java.lang.Object
, V extends java.lang.Object
。 类型本身( Map
)也扩展了类java.lang.Object
,没有接口。
Javagenerics确实是通过类型擦除实现的,因此字节码中没有类型信息。
例如,让我们看看两个声明List
字段的类,一个是generics的,另一个是非generics的:
class NonGeneric { List list; }
和,
class Generic { List list; }
在这两种情况下,生成的字节码如下:
Code: Stack=3, Locals=1, Args_size=1 0: aload_0 1: invokespecial #1; //Method java/lang/Object."":()V 4: aload_0 5: new #2; //class java/util/ArrayList 8: dup 9: invokespecial #3; //Method java/util/ArrayList." ":()V 12: putfield #4; //Field list:Ljava/util/List; 15: return
没有引用ArrayList
和List
使用的String
类型。 因此,我们可以看到generics确实是通过类型擦除来实现的。
但是,如果我们查看常量池,我们可以找到差异。
非通用常量池:
Constant pool: const #1 = Method #6.#15; // java/lang/Object."":()V const #2 = class #16; // java/util/ArrayList const #3 = Method #2.#15; // java/util/ArrayList." ":()V const #4 = Field #5.#17; // NonGeneric.list:Ljava/util/List; const #5 = class #18; // NonGeneric const #6 = class #19; // java/lang/Object const #7 = Asciz list; const #8 = Asciz Ljava/util/List;; const #9 = Asciz ; const #10 = Asciz ()V; // snip the rest //
通用常量池:
Constant pool: const #1 = Method #6.#17; // java/lang/Object."":()V const #2 = class #18; // java/util/ArrayList const #3 = Method #2.#17; // java/util/ArrayList." ":()V const #4 = Field #5.#19; // Generic.list:Ljava/util/List; const #5 = class #20; // Generic const #6 = class #21; // java/lang/Object const #7 = Asciz list; const #8 = Asciz Ljava/util/List;; const #9 = Asciz Signature; const #10 = Asciz Ljava/util/List;; const #11 = Asciz ; const #12 = Asciz ()V; // snip the rest//
可以看出,在Generic
类中,我们可以看到常量池中有两个额外的常量#9
和#10
,它们提到List
具有generics类型的String
。
(并结合我从Chris Jester-Young的答案中学到的新知识)
进一步查看类文件的反汇编,在Code: block
的Generic
类之前有一个常量#10的引用:
java.util.List list; Signature: length = 0x2 00 0A
hex值0A
为十进制10
,表示常量池#10
:
const #10 = Asciz Ljava/util/List;;
因此,使用来自常量池的信息以指示字段是generics类型。