26

据我所知,Java 在运行时丢弃了泛型类型参数信息。它仅用于编译以执行检查,例如,此特定方法调用是否有效。

今天我遇到了以下一段代码,其中,Java 似乎通过集合/列表类型参数来确定要调用哪个构造函数:

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");
    }
}

进行以下调用:

new MyClass(new HashSet<String>()); // Constructor 1
new MyClass(new ArrayList<String>()); // Constructor 1
new MyClass(new ArrayList<Integer>()); // Constructor 2

现在,如果我删除类型参数:

public static class MyClass2 {
    public MyClass2(final Collection coll) {
        System.out.println("Constructor 1");
    }
    public MyClass2(final List list) {
        System.out.println("Constructor 2");
    }
}

...相同的呼叫行为与我期望的一样;使用列表参数的构造函数调用适用于“最精确”满足其需求的构造函数:

new MyClass2(new HashSet<String>()); // Constructor 1
new MyClass2(new ArrayList<String>()); // Constructor 2
new MyClass2(new ArrayList<Integer>()); // Constructor 2

似乎泛型信息存储在编译的类(在本例中为 MyClass)中,毕竟没有被丢弃,但它应该被丢弃。我有什么误解?

4

3 回答 3

17

这里发生的是编译器可以通过使用泛型来区分这两个构造函数,所以它创建字节码之前和剥离泛型之前这样做。

在中间情况下,它将告诉 VM 调用MyClass2<init>(Collection)(即生成与此特定构造函数匹配的字节码)。

VM 不会尝试在运行时确定哪个方法匹配。那太慢了。相反,它依赖于编译器创建非常具体的指令。

这就是为什么即使通用信息在运行时已被删除,上面的代码仍然有效的原因。

[编辑]澄清:字节码包含编译器可以看到和使用的附加信息。您可以通过反射获得相同的信息。

擦除意味着字节码解释器和 JIT 不关心泛型,这就是为什么你不能在同一个类中拥有setFoo(List<String>)setFoo(List<Integer>):虽然编译器可以区分两者,但运行时不能。

具体来说,当您通过反射检查方法时,您将获得泛型信息,但字节码解释器/JIT 不使用反射。相反,它使用压缩的方法签名,其内容类似于Method com/pany/Type/setFoo(Ljava.util.List;)V- 这里不再有泛型。

有关的:

于 2013-08-26T14:44:41.683 回答
4

没有注意到您正在处理构造函数。无论如何,即使对于构造函数,以下参数也是有效的。


重载方法的方法调用由编译器在编译时解析。泛型仅在编译时用于类型检查。所以,这与类型擦除无关,这完全是一个运行时业务。

考虑你的第一个案例:

public MyClass(final Collection<String> coll)
public MyClass(final List<Integer> list)

现在,当您调用该方法时:

new MyClass(new HashSet<String>()); // Constructor 1
new MyClass(new ArrayList<String>()); // Constructor 1
new MyClass(new ArrayList<Integer>()); // Constructor 2

编译器将决定哪个方法对传递的参数具有更具体的类型。考虑案例 2,我猜你的主要疑问在哪里。

ArrayList<String>是 的子类型Collection<String>,但不是 的子类型List<Integer>。泛型是不变的。因此,编译器会将第二个方法调用绑定到第一个方法。

现在,考虑您使用原始类型的第二种情况:

public MyClass2(final Collection coll)
public MyClass2(final List list)

现在,ArrayList<String>ListCollection两者的子类型。但比List更具体。因此,编译器将绑定方法调用:ArrayListCollection

new MyClass2(new ArrayList<String>());

以一个List作为论据。


参考:

于 2013-08-26T14:47:31.647 回答
3

简单来说,编译器选择匹配的最窄类型。

在原始参数版本中,很明显会选择什么,但在泛型版本中,集合/列表的类型包含在匹配信息中:一个 ArrayList 既是集合又是列表,但是您可以看到它很容易通过比较 ArrayList 的类型来打破平局 - 现在只有一种方法仍然匹配。

于 2013-08-26T14:46:43.877 回答