【问题标题】:Java 8 type inference cause to omit generic type on invocationJava 8 类型推断导致在调用时省略泛型类型
【发布时间】:2016-11-24 12:35:04
【问题描述】:

升级到 Java 1.8 后,我遇到了泛型方法的问题,Java 1.6 和 1.7 都可以 考虑以下代码:

public class ExtraSortList<E> extends ArrayList<E> {
    ExtraSortList(E... elements) {
        super(Arrays.asList(elements));
    }

    public List<E> sortedCopy(Comparator<? super E> c) {
        List<E> sorted = new ArrayList<E>(this);
        Collections.sort(sorted, c);
        return sorted;
    }

    public static void main(String[] args) {
        ExtraSortList<String> stringList = new ExtraSortList<>("foo", "bar");

        Comparator<? super String> compGen = null;
        String firstGen = stringList.sortedCopy(compGen).get(0); // works fine

        Comparator compRaw = null;
        String firstRaw = stringList.sortedCopy(compRaw).get(0); // compiler ERROR: Type mismatch: cannot convert from Object to String
    }
}

我使用 Oracle javac (1.8.0_92) 和 Eclipse JDT (4.6.1) 编译器尝试了这个。两者的结果相同。 (报错信息有点不同,但本质上是一样的)

除此之外,可以通过避免原始类型来防止错误,这让我感到困惑,因为我不明白其中的原因。

为什么 sortedCopy-Method 的 raw 方法参数对返回值的泛型类型有影响?泛型类型已在类级别定义。该方法没有定义单独的泛型类型。引用 list 被键入到 &lt;String&gt;,返回的 List 也是如此。

为什么 Java 8 会在返回值上从类中丢弃泛型类型?

编辑:如果 sortedCopy 的方法签名被更改(如 biziclop 所指出)为

public List<E> sortedCopy(Comparator c) {

那么编译器确实会考虑来自类型ExtraSortList&lt;E&gt; 的泛型类型E,并且不会出现错误。但是现在参数c 是原始类型,因此编译器无法验证提供的 Comparator 的泛型类型。

编辑:我对 Java 语言规范进行了一些审查,现在我想,我是否缺乏理解,或者这是编译器中的一个缺陷。因为:

  • 泛型类型EScope of a Declaration 是类ExtraSortList,其中包括方法sortedCopy
  • sortedCopy 方法本身没有声明一个泛型类型变量,它只是从类作用域引用类型变量E。请参阅 JLS 中的Generic Methods
  • JLS 也在同一部分中声明

    在调用泛型方法时,可能不需要显式提供类型参数,因为它们通常可以被推断(第 18 节(类型推断))。

  • 引用stringList 是用String 定义的,因此编译器不需要在调用sortedCopy 时推断E 的类型,因为它已经定义了。
  • 因为stringList 已经具有E 的具体类型,所以对于给定的调用,参数c 应该是Comparator&lt;? super String&gt;
  • 返回类型也应该使用已经具体化的类型E,因此应该是List&lt;String&gt;

这是我目前对 Java 编译器应该如何评估调用的理解。如果我错了,解释为什么我的假设是错误的会很好。

【问题讨论】:

  • “我的错误在哪里?”在使用原始类型时。您知道正确的做法,就像上面的示例一样 - 在 Comparator 之后添加 &lt;String&gt;
  • 实际上对我来说都很好。 (1.8.0_45)
  • @KevinEsche 和我一样!
  • 在实践中,“不使用原始类型”当然是​​一个足够的答案,但仍然是一个有趣的问题,即类型推断规则发生了什么变化导致它变得无效。 (如果它根本无效。)

标签: java generics


【解决方案1】:

为为什么会发生这种情况提供最终答案:

就像@Jesper 已经提到的那样,您在不应该使用原始类型时使用了原始类型(尤其是在多种情况下使用 Generic 作为类型时)。 由于您传递了没有 Generic-Type 的 Comparator,因此实际上没有。您可以将 E-Generic 视为 null 以使其更容易。因此你的代码变成了这样:

public List sortedCopy(Comparator c) {
    List sorted = new ArrayList(this);
    Collections.sort(sorted, c);
    return sorted;
}

现在你正在尝试/假设你从没有泛型的列表中得到一个字符串,因此是一个对象(因此它是一切的超类)。

关于为什么原始类型参数对返回类型没有影响的问题,因为您没有指定某个抽象级别。例如,您必须定义一个 Generic 必须扩展/实现的类型,以使其发生(编译错误)。

public class ExtraSortList<E extends String> extends ArrayList<E> {

现在只允许扩展它的字符串或类(这里不可能,因为字符串是最终的)。这样,您的后备类型将是 String。

【讨论】:

  • 您的回答清楚地表明,Java 编译器先于方法参数中的泛型类型,然后再出现在被调用的“列表”引用中已经存在的泛型类型。我会检查 JLS 是否有任何线索。因为1.7和1.8之间肯定有变化
  • @MaikScheibler 也可能是旧的javac 处理它的方式不符合规范。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-04-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多