据我所知,Java在运行时删除泛型类型参数信息.它仅用于编译以执行检查,例如,此特定方法调用是否有效.
今天我遇到了下面这段代码,其中,看似Java,由collection/list类型参数决定,要调用哪个构造函数:
public static class MyClass {
public MyClass(final Collection<String> coll) {
System.out.println("Constructor 1");
}
public MyClass(final List<Integer> list) {
System.out.println("Constructor 2");
}
}
Run Code Online (Sandbox Code Playgroud)
进行以下调用:
new MyClass(new HashSet<String>()); // Constructor 1
new MyClass(new ArrayList<String>()); // Constructor 1
new MyClass(new ArrayList<Integer>()); // Constructor 2
Run Code Online (Sandbox Code Playgroud)
现在,如果我删除类型参数:
public static class MyClass2 {
public MyClass2(final Collection coll) {
System.out.println("Constructor 1");
}
public MyClass2(final List list) {
System.out.println("Constructor 2");
}
}
Run Code Online (Sandbox Code Playgroud)
......同样的电话就像我期望的那样; 使用list参数的构造函数调用适用于"最精确地"满足其需求的构造函数:
new MyClass2(new HashSet<String>()); // Constructor 1
new MyClass2(new ArrayList<String>()); // Constructor 2
new MyClass2(new ArrayList<Integer>()); // Constructor 2
Run Code Online (Sandbox Code Playgroud)
似乎泛型信息存储在已编译的类(在本例中为MyClass)中,并且毕竟不会被丢弃,但应该将其丢弃.我有什么误会?
Aar*_*lla 17
这里发生的是编译器可以通过使用泛型来区分这两个构造函数,因此它在创建字节代码之前和剥离泛型之前就已经完成了.
在中间情况下,它将告诉VM调用MyClass2<init>(Collection)(即生成与此特定构造函数匹配的字节代码).
VM不会尝试确定哪个方法在运行时匹配.那太慢了.相反,它依赖于编译器创建非常具体的指令.
这就是为什么上面的代码即使在运行时擦除了通用信息也能工作的原因.
[编辑]澄清:字节代码包含编译器可以看到和使用的附加信息.您可以通过反射获得相同的信息.
擦除意味着字节码解释器和JIT不关心泛型,这就是为什么你不能拥有setFoo(List<String>)和setFoo(List<Integer>)在同一个类中:虽然编译器可以区分这两者,但运行时却不能.
具体来说,当您通过反射检查方法时,您将获得泛型信息,但字节码解释器/ JIT不使用反射.相反,它使用压缩方法签名,其中的内容类似于Method com/pany/Type/setFoo(Ljava.util.List;)V- 此处不再使用泛型.
有关: