具有仿制药的奇怪编译器行为:在继承上缩小返回类型的参数



在覆盖上,如果仅涉及原始/未基于的类型,则不需要其他类型变量来缩小超级方法的返回类型。示例:在继承上缩小返回类型(涉及仿制药)

但是,如果类型参数被缩小 - 虽然原始类型保持不变 - 则需要附加的方法类型变量。

示例:

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()中未使用的变量如何使编译器关闭?

当您用协变返回类型的方法覆盖方法时,您实际上是在说覆盖返回类型是超级返回类型的子类。表示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

您可以用NumberInteger替换A<T>,用CC_11和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();
}

我认为诀窍在于,覆盖方法不够"通用"(我知道缺乏更好的术语),请查看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;>;

请注意,覆盖方法缺乏<>,我想这类似于使用原始类型,因此未检查的警告而不是错误:它使用部分删除的类型。

这是我放弃的点,也许安迪或杰恩可以更正式的解释。

在原始类型和通用类型混合的情况下,语言规格放弃了。否则将引入更加晦涩的规则,所有这些都支持应避免的情况。

A.testList3是一种通用方法。B试图声明与非生成相同的方法(相同的名称,与参数相同的类型和返回)。因此,似乎语言规格忽略了所有兼容方法的仿制药。

(所以注意,不要抑制,警告。)

最新更新