Java 使用泛型类型参数确定要调用哪个方法?

2022-09-01 17:42:08

据我所知,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)中,并且毕竟没有被丢弃,但应该被丢弃。我误解了什么?


答案 1

这里发生的事情是编译器可以使用泛型来区分这两个构造函数,因此它在创建字节代码之前和剥离泛型之前都会这样做。

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

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

这就是为什么即使通用信息在运行时已被擦除,上述代码也可以正常工作的原因。

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

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

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

相关:


答案 2

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


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

考虑您的第一个案例:

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>ListCollectionListArrayListCollection

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

以论据为依据。List


引用:


推荐