覆盖方法中的泛型

Posted

技术标签:

【中文标题】覆盖方法中的泛型【英文标题】:Generics in overridden methods 【发布时间】:2013-04-04 04:04:20 【问题描述】:

遇到了一个有趣的问题;以下类编译:

public class Test 

    public static void main(String[] args) throws Exception 
        A a = new A();
        B b = new B();

        foo(a);
        foo(b);
       

    private static void foo(A a) 
        System.out.println("In A");
       

    private static void foo(B b) 
        System.out.println("In B");
       

    private static class A 

    private static class B extends A 


但是这个失败了:

public class Test 

    public static void main(String[] args) throws Exception 
        A<String> a = new A<>();
        B b = new B();

        foo(a);
        foo(b);
       

    private static void foo(A<String> a) 
        System.out.println("In A");
       

    private static void foo(B b) 
        System.out.println("In B");
       

    private static class A<T> 

    private static class B extends A 


出现此错误:

Test.java:8: error: reference to foo is ambiguous, both method foo(A<String>) in Test and method foo(B) in Test match              
        foo(b);                                                                                                                    
        ^                                                                                                                          
Note: Test.java uses unchecked or unsafe operations.                                                                               
Note: Recompile with -Xlint:unchecked for details.                                                                                 
1 error

我原以为由于类型擦除,这些将基本相同。有人知道这里发生了什么吗?

【问题讨论】:

A&lt;String&gt; a = new A&lt;&gt;(); 是什么? A&lt;String&gt; a = new A&lt;String&gt;(); 怎么样? @Trinimon 这只是 Java7 A a = new A(); 的简写符号; 【参考方案1】:

原因是您混合了泛型和原始类型(B 应声明为 class B&lt;T&gt; extends A&lt;T&gt;class B extends A&lt;SomeType&gt;)。

发生这种情况的实际原因被埋在the JLS, section #15.12.2.7 的某个地方,然后 - 祝你好运,简洁地表达它;-)

【讨论】:

与@Louis 的回答相同。仅靠这种变化是不够的。 我认为#15.12.2.5 部分在这里可能更适用。编译器不会试图推断类型;它试图决定调用哪个方法。在我看来,问题应该是,为什么编译器不认为foo(B)foo(A&lt;String&gt;) 更具体,因为它知道bB 类型? 我看起来可能是这些规范的某种组合,15.12.2.5 引用了使用 15.12.2.7 中的过程推断的类型。挖掘那些我绝对不是他们的符号的粉丝 - 似乎他们可以选择更具表现力的东西。 @matts 是的,确实可以归结为找出一种方法是否比另一种更具体,这取决于 15.2.2.7 何时涉及泛型。老实说,我没有仔细看,因为它真的很乱。【参考方案2】:

在泛型出现之前,Java 有类似的方法

public class Collections

    public void sort(List list) ...               [1]

而用户代码可能有类似的东西

public class MyList implements List ...             [2]

MyList myList = ...;
Collections.sort(myList);                           [3]

当泛型被添加到 Java 中时,我们决定将现有类和方法转换为泛型类和方法,而不会破坏任何使用它们的代码。就难度而言,这是一项了不起的成就,但代价是让语言变得复杂和有缺陷。

所以[1] 已泛化,但[3] 仍必须按原样编译,而不必泛化[2]

黑客在§15.12.2.3

可以通过方法调用转换(§5.3)将Ai转换为Si

基本上说如果参数类型(Ai)是原始的,那么为了匹配的目的也删除参数类型(Si)。

回到你的例子,我们知道为什么foo(A&lt;String&gt;) 被认为适用于foo(b)

但是还有另一个问题 - foo(A&lt;String&gt;) 是否适用于 [§15.12.2.2]?从规范的字母来看,答案似乎是“不”。但这可能是规范的错误。

【讨论】:

对,foo(A) 特意适用于使用 B 类型的参数进行调用以实现向后兼容性。奇怪的是这个逻辑是如何与 15.12.2.5 中指定的逻辑交互的,因此 foo(B) 不再被认为是 foo(b) 调用的最大特定。 好吧,B 不是A&lt;String&gt; 的子类型,因此foo(B) 并不比foo(A&lt;String&gt;) 更具体 那么,奇怪的是 B 被认为是 A 的子类型,用于方法调用目的(用于 bw compat),而不是用于确定最具体的方法?我想我需要再次重新阅读规范,这似乎是一种处理这种情况的非常奇怪的方式。 规范在原始类型上写得不是很好。 同意。再次查看 JLS,我认为它是这样写的。兼容性黑客似乎在 15.12.2.2 中(Ai 可通过未经检查的转换(§5.1.9)转换为某种类型的 Ci,并且 Ci <: si>【参考方案3】:
private static class B extends A 

您在此处省略了 A 的类型参数。你的意思可能是

private static class B<T> extends A<T> 

此外,B b = new B() 也没有 &lt;String&gt; 参数;你必须这样做

B<String> b = new B<String>();

...最后,

private static void foo(B b) 
    System.out.println("In B");
 

应该是这样的

private static void foo(B<String> b) 
    System.out.println("In B");
 

一般来说,如果有一个类型 Foo 有一个泛型参数,Foo 基本上应该总是有一个类型参数。在这种特殊情况下,class B extends A 没有A 的类型参数,然后B 需要一个类型参数,因此您在提到B 的任何地方都需要一个类型参数。 (该规则的一个主要例外是 instanceof 表达式,但这里没有。)

【讨论】:

是的,在不指定类型参数的情况下引用参数化类型绝对是禁忌。我遇到了一些扩展 java.util 集合接口的旧类,这些接口从未更新为处理泛型。问题更多的是当泛型应用于 A 时,类型推断规则有什么不同。

以上是关于覆盖方法中的泛型的主要内容,如果未能解决你的问题,请参考以下文章

Java泛型边界问题,super关键字

java中的泛型方法

方法中的泛型--同一方法中使用多个泛型 子类

C#中的泛型是啥意思?

java中的泛型 求详细解释

Java 泛型泛型简介 ( 泛型类 | 泛型方法 | 静态方法的泛型 | 泛型类与泛型方法完整示例 )