【问题标题】:Generic method does not compile when passed a generic argument传递泛型参数时,泛型方法无法编译
【发布时间】:2017-07-28 01:21:18
【问题描述】:

在实现大量使用泛型的 Java 接口时,我发现了一个奇怪的不一致之处,我无法解释为什么会发生这种情况。

我已经尽可能地精简了这个例子,而且我知道在这个例子中使用泛型已经没有多大意义了。

public interface Service<T> {
    public List<T> thisCompiles();
    public List<T> andThisCompiles(List<Object> inputParam);
    public <S extends T> List<S> thisCompilesAswell();
    public <S extends T> List<S> evenThisCompiles(List inputParam);
    public <S extends T> List<S> butThisDoesnt(List<Object> inputParam);
}

public class ServiceImpl implements Service<Number> {

    @Override
    public List<Number> thisCompiles() {
        return null;
    }

    @Override
    public List<Number> andThisCompiles(List<Object> inputParam) {
        return null;
    }

    @Override
    public List<Number> thisCompilesAswell() {
        return null;
    }

    @Override
    public List<Number> evenThisCompiles(List inputParam) {
        return null;
    }

    @Override
    public List<Number> butThisDoesnt(List<Object> inputParam) {
        return null;
    }
}

请注意,在实现中,所有返回类型都是List&lt;Number&gt;,虽然接口更慷慨。

在编译这个 sn-p 时(我尝试过 Oracle JDK 8 u144 和 OpenJDK 8 u121),我收到以下错误消息:

  • ServiceImpl 类型的方法 butThisDoesnt(List&lt;Object&gt;) 必须重写或实现超类型方法
  • 类型ServiceImpl必须实现继承的抽象方法Service&lt;Number&gt;.butThisDoesnt(List&lt;Object&gt;)
  • 名称冲突:ServiceImpl 类型的方法 butThisDoesnt(List&lt;Object&gt;)Service&lt;T&gt; 类型的 butThisDoesnt(List&lt;Object&gt;) 具有相同的擦除,但不会覆盖它

似乎只要我传递给函数的参数本身具有一些通用参数,编译就会失败。

实现第5个函数

    @Override
    public <S extends Number> List<S> butThisDoesnt(List<Object> inputParam) {
        return null;
    }

按预期工作。

是否有对这种行为的解释或者我偶然发现了一个错误(尽管它也发生在 OpenJDK 中)?为什么第 5 个函数的行为不同?

我对如何编译这段代码不感兴趣(我为我的代码库修复了它)。我只是偶然发现了这个问题,并对逻辑解释为什么编译器接受前四种方法但拒绝第五种方法感到好奇。

【问题讨论】:

  • &lt;S extends Number&gt; List&lt;S&gt;List&lt;Number&gt; 不同,因此第一个版本不会覆盖抽象方法。所以很明显你还没有实现所有的方法。很明显你实际上并没有覆盖任何东西,所以你的@Override 很糟糕。最后,您遇到了名称冲突,因为method(List&lt;A&gt; a)method(List&lt;B&gt; b) 的签名相同,如下面的答案所述。
  • @matt 检查方法 3 和 4(thisCompilesAswellevenThisCompiles)。它们还返回List&lt;Number&gt;,同时在接口中定义为&lt;S extends T&gt; List&lt;S&gt;,但它们可以编译。名称冲突只是@Override 不起作用的结果。
  • 然后删除@Override,我认为您仍然会遇到名称冲突。我真的很惊讶其他方法编译。类型擦除表示它们具有相同的签名。
  • @matt 相同的签名不同的名字?
  • @CarlosHeuberger 什么?他说名称冲突是因为 Override 不起作用。不是,这是因为他有一个方法butThisDoesnt(List&lt;Object&gt; inputParam),它与接口中的相同方法发生冲突。不是实际的 @ 注释。

标签: java generics


【解决方案1】:

我已包含该问题的简化版本。在下面的例子中,由于方法broken,程序将无法编译。

import java.util.List;
import java.util.ArrayList;

public class Main{
    static interface Testing{

        <S> List<S> getAList();
        <S> List<S> broken(List<String> check);
    }

    static class Junk implements Testing{
        public List<Number> getAList(){
            return new ArrayList<>();
        }
        public List<Number> broken(List<String>check){
            return new ArrayList<>();
        }
    }

    public static void main (String[] args) throws java.lang.Exception
    {
        // your code goes here
    }
}

有两个错误和一个警告。

  • Main.java:11:错误:垃圾不是抽象的,也不会覆盖
    测试静态类垃圾中的抽象方法损坏(列表) 实施测试{ ^ 其中 S 是一个类型变量: S 扩展了方法中声明的 Object (List)

  • Main.java:12:警告:垃圾实现中的 [未选中] getAList() 测试中的 getAList() 公共列表 getAList(){ ^ 返回类型需要从 List 到 List 的未经检查的转换,其中 S 是类型变量: S 扩展方法 getAList() 中声明的 Object

  • Main.java:15:错误:名称冲突:垃圾中的损坏(列表)和 测试中的损坏(列表)具有相同的擦除,但两者都没有 覆盖其他公共列表损坏(Listcheck){ ^ 其中 S 是一个类型变量: S 扩展了方法中声明的 Object (List)

2 个错误 1 个警告

为什么第一种方法会推断强制转换并且只给出警告?

“尽管不合理,但定义中允许未经检查的转换,作为允许从非泛型代码平滑迁移到泛型代码的特殊允许。”

https://docs.oracle.com/javase/specs/jls/se8/html/jls-8.html#jls-8.4.5

另一个方法在参数列表中有一个泛型,因此它不是从以前的非泛型代码的迁移,它是不健全的,应该被认为是一个错误。

【讨论】:

  • 哦,我明白了,那个脚注解释了错误。感谢您挖掘它。
【解决方案2】:

您的第五种方法的问题是人们可能认为以下 sn-p 有效:

List<Integer> ints = new ArrayList<>(); // this works as expected
List<Number> number = new ArrayList<>(); // this works too
number = ints; // this doesn't work

也就是说,因为泛型不存在层次关系之类的东西,所以List&lt;Number&gt;List&lt;Integer&gt; 毫无关系。

这是由于类型擦除,泛型仅在编译期间使用。在运行时,您只有 List-objects(类型擦除 = 在运行时不再可用类型)。

在接口中,方法定义为&lt;S extends T, V&gt; List&lt;S&gt; butThisDoesnt(List&lt;V&gt; inputParam);,其中S 可以是Number 的任何子类型。如果我们现在将上述内容应用到您的实现中,那么它应该是有意义的。

public List<Number> butThisDoesnt(List<Object> inputParam) {
    return null;
}

List&lt;Number&gt; 与接口中的定义冲突,因为List&lt;Number&gt; 可以包含任何Number 但不能是List&lt;Integer&gt;

阅读更多内容以键入擦除 in this other question。还有this page

注意如果我错了或者我确实遗漏了什么,请纠正我。

【讨论】:

  • 为什么应该能够在运行时区分 List&lt;Object&gt;List&lt;Number&gt; 在这种特定情况下的帮助(或者为什么不能导致问题)?
  • 你看过thisCompilesAswellevenThisCompiles这两个函数了吗?它们的定义方式相同(只是没有为 inputParam 使用泛型),但它们可以编译。
  • @sina 我不得不承认我不能给你一个正确的答案......我也很困惑,我想我会再次删除答案
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-07-17
  • 1970-01-01
  • 2014-06-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多