使用泛型的奇怪编译器行为:在继承上缩小返回类型参数

时间:2017-02-26 15:06:45

标签: java generics

在重写时,如果仅限于原始/未删除类型,则不需要额外的类型变量来缩小super方法的返回类型。示例:Narrowing return types on inheritance (generics involved)

但是如果类型参数变窄 - 原始类型保持不变 - 则需要一个额外的方法类型变量。

示例:

import java.util.List;

public class InheritanceTest {

    public interface A<T> {

        A<T> test();

        List<A<T>> testList1();

        <U extends A<T>> List<U> testList2();

        <U> List<A<T>> testList3();

        // extending class/interface does not need to suppress warnings:
        List<? extends A<T>> testList4();
    }

    public interface B extends A<Integer> {

        @Override
        B test();

        @Override
        List<B> testList1();

        @Override @SuppressWarnings("unchecked")
        List<B> testList2();

        @Override @SuppressWarnings("unchecked")
        List<B> testList3();

        @Override
        List<B> testList4();
    }
}

编译器抱怨testList1()的返回类型不兼容,但没有报告其余三种方法的任何错误。 `

testList3()中未使用的变量如何使编译器闭嘴?

2 个答案:

答案 0 :(得分:1)

当你用一个协变返回类型覆盖一个方法时,你实际上是说重写的返回类型是超级返回类型的子类。含义List<B>可赋予List<A<T>>,但这不是真的。这是一个简单的示例,没有使用您的类型(A<T>B的方法声明;他们在这里有什么方法不重要):

List<A<Integer>> listOfA;
List<B> listOfB =  new ArrayList<>();
listOfA = listOfB; // compile error: incompatible types: List<B> cannot be converted to List<A<Integer>>

List<? extends A<Integer>> listOfAFixed;
listOfAFixed = listOfB;

...以及为什么不允许这个赋值的原因,因为可能有一个C extends A<Integer>会引入ClassCastException的潜力

List<C> listOfC =  new ArrayList<>();
listOfA = listOfC; // let's assume this compiles
B b = getAB();
listOfA.add(b); // adding to listOfC, right?
C c = listOfC.get(0); // whops, ClassCastException

您可以将A<T>替换为Number,将B替换为Integer,将C替换为Float;同样的事情会发生。 问题不在于A上的泛型,而在于List的泛型。

使用相同的逻辑我也试图在方法体中重现其他协变覆盖(〜赋值),但它们也没有发出警告但也有错误。这很奇怪。

有一点需要注意的是,从A删除参数仍然会发出相同的警告

public interface A {
    <U extends A> List<U> testList2();
    <U> List<A> testList3();
}

public interface B extends A {
    @Override List<B> testList2();
    @Override List<B> testList3();
}

我认为诀窍在于最重要的方法不够通用&#34; (我知道缺乏更好的术语),请查看javap列出的内部签名:

<U::LInheritanceTest$A;>()Ljava/util/List<TU;>;
()Ljava/util/List<LInheritanceTest$B;>;

<U:Ljava/lang/Object;>()Ljava/util/List<LInheritanceTest$A;>;
()Ljava/util/List<LInheritanceTest$B;>;

请注意,重写方法缺少<>,我猜这类似于使用原始类型,因此未经检查的警告而不是错误:它使用部分擦除类型。

这是我放弃的地方,也许Andy或Jorn可以更正式地解释这一点。

答案 1 :(得分:0)

在原始类型和泛型类型混合的情况下,语言规范放弃了。否则会引入更加模糊的规则,以支持应该避免的情况。

A.testList3是一种通用方法。 B尝试声明与非泛型相同的方法(相同名称,与参数相同的已擦除类型并返回)。因此,似乎语言规范忽略了使方法兼容的所有泛型。

(注意,不要压制,警告。)