如果我们应用类型擦除,哪些重载方法将在运行时被调用,为什么?

Which of the overloaded methods will be called on runtime if we apply type erasure, and why?

假设我们有以下泛型 class

public class SomeType<T> {
    public <E> void test(Collection<E> collection){
        System.out.println("1st method");

        for (E e : collection){
            System.out.println(e);
        }
    }

    public void test(List<Integer> integerList){
        System.out.println("2nd method");

        for (Integer integer : integerList){
            System.out.println(integer);
        }
    }

}

现在在 main 方法中我们有以下代码片段

SomeType someType = new SomeType();
List<String> list = Arrays.asList("value");
someType.test(list);

作为执行 someType.test(list) 的结果,我们将在控制台和 java.lang.ClassCastException 中获得“第二种方法”。据我了解,执行第二个 test 方法的原因是我们不对 SomeType 使用泛型。因此,编译器会立即从 class 中删除所有泛型信息(即 <T><E>)。在执行完第二个 test 方法后,将 List integerList 作为参数,当然 ListList 的匹配程度优于 Collection.

现在考虑在 main 方法中我们有以下代码片段

SomeType<?> someType = new SomeType<>();
List<String> list = Arrays.asList("value");
someType.test(list);

在这种情况下,我们将在控制台中获得“第一种方法”。这意味着第一个测试方法正在执行。问题是为什么?

根据我对运行时的理解,由于类型擦除,我们从来没有任何泛型信息。那么,为什么第二个 test 方法无法执行。对我来说,第二个 test 方法应该(在运行时)采用以下形式 public void test(List<Integer> integerList){...} 不是吗?

The JLS is a bit of a rat's nest on this one,但是有一个非正式的(他们的话,不是我的)规则你可以使用:

[O]ne method is more specific than another if any invocation handled by the first method could be passed on to the other one without a compile-time error.

为了论证,让我们调用<E> test(Collection<E>)方法1,test(List<Integer>)方法2。

让我们在这里扔一个扳手 - 我们知道整个 class 是通用的,所以在没有 some 类型的情况下实例化它会产生... 不理想 运行时类型检查。

另一部分是由于 ListCollection 更具体,如果一个方法被传递 List,它会寻求容纳更多比 Collection 更容易,但需要注意的是应该在编译时检查类型。由于它 不是 原始类型,我相信这个特定的检查被跳过,并且 Java 将 List<Integer> 视为 更具体Collection<capture(String)>.

您应该提交 JVM 错误,因为这看起来不一致。或者,至少,让编写 JLS 的人用比他们古怪的数学符号稍微好一点的英语解释为什么这是合法的...

继续前进;对于你的第二个例子,你礼貌地把你的实例输入为通配符,这允许 Java 做出正确的编译时断言 test(Collection<E>) 最安全的 方法选择。

请注意,其中 none 这些检查发生在运行时。这些都是在 Java 运行之前做出的所有决定,因为模棱两可的方法调用或使用不受支持的参数调用方法会导致 编译时间 错误。

故事的寓意:不要使用原始类型。它们是邪恶的。 它使类型系统以奇怪的方式运行,它实际上只是为了保持向后兼容性。

适用的方法匹配之前类型擦除(see JSL 15.12.2.3)。 (擦除意味着运行时类型没有参数化,但是方法是在编译时选择的,当类型参数可用时)

list的类型是List<String>,因此:

  • test(Collection<E>)适用的,因为List<Integer>兼容Collection<E>,其中EInteger(形式上,约束公式 List<Integer> → Collection<E> [E:=Integer] 简化为 true,因为 List<Integer>Collection<Integer> 的子类型)。

  • test(List<String>)不适用,因为List<String>不兼容List<Integer>(形式上,约束公式List<String>List<Integer> 减少为 false 因为 String 不是 Integer) 的超类型).

详情解释隐藏在JSL 18.5.1

对于test(Collection<E>)

Let θ be the substitution [E:=Integer]

[...]

A set of constraint formulas, C, is constructed as follows: let F1, ..., Fn be the formal parameter types of m, and let e1, ..., ek be the actual argument expressions of the invocation.

在这种情况下,我们有 F1 = Collection<E>e1 = List<Integer>

Then: [the set of constraint formulas] includes ‹ei → Fi θ›

在这种情况下,我们有 List<Integer> → Collection<E> [E:=Integer](其中 → 表示在推断出类型变量 E 之后 e1 与 F1 兼容)

对于test(List<String>),没有替换(因为没有推理变量),约束只是List<String>List<Integer>.