繁体   English   中英

Java自动返回类型协方差与泛型子类

[英]Java automatic return type covariance with generic subclassing

我有两个看起来像这样的接口:

interface Parent<T extends Number> {
    T foo();
}

interface Child<T extends Integer> extends Parent<T> {
}

如果我有一个原始的Parent对象,则调用foo()默认返回一个Number因为没有type参数。

Parent parent = getRawParent();
Number result = parent.foo(); // the compiler knows this returns a Number

这是有道理的。

如果我有一个原始的Child对象,我希望调用foo()会以相同的逻辑返回一个Integer 但是,编译器声称它返回一个Number

Child child = getRawChild();
Integer result = child.foo(); // compiler error; foo() returns a Number, not an Integer

我可以覆盖Parent.foo()Child ,以解决这个问题,就像这样:

interface Child<T extends Integer> extends Parent<T> {
    @Override
    T foo(); // compiler would now default to returning an Integer
}

为什么会这样? 有没有办法让Child.foo()默认返回一个Integer而不覆盖Parent.foo()

编辑:假装Integer不是最终的。 我只选择了NumberInteger作为例子,但显然它们不是最好的选择。 :S

  1. 这是基于@AdamGent的想法。
  2. 不幸的是,我不熟悉JLS足以证明以下规范。

想象一下public interface Parent<T extends Number>是在另一个编译单元中定义的 - 在单独的文件Parent.java

然后,在编译Childmain ,编译器会将方法foo看作Number foo() 证明:

import java.lang.reflect.Method;
interface Parent<T extends Number> {
    T foo();
}

interface Child<R extends Integer> extends Parent<R> {
}

public class Test {
    public static void main(String[] args) throws Exception {
        System.out.println(Child.class.getMethod("foo").getReturnType());
    }
}

打印:

class java.lang.Number

该输出是合理作为Java并类型擦除并且不能够保留T extends在结果.class文件加上因为方法foo()只在定义的Parent 要更改子编译器中的结果类型,需要将一个存根 Integer foo()方法插入到Child.class字节码中。 这是因为编译后仍然没有关于泛型类型的信息。

现在,如果你修改你的孩子:

interface Child<R extends Integer> extends Parent<R> {
    @Override R foo();
}

例如,将自己的foo()添加到Child ,编译器将使用不同但仍兼容的原型Integer foo().class文件中创建Child自己的方法副本。 现在输出是:

class java.lang.Integer

这当然令人困惑,因为人们会期望“词汇可见性”而不是“字节码可见性”。

另一种方法是编译器在两种情况下以不同的方式编译它:接口在相同的“词法范围”中,当编译器只能看到字节码时,编译器可以在不同的编译单元中看到源代码和接口。 我不认为这是一个很好的选择。

T s并不完全相同。 想象一下,接口是这样定义的:

interface Parent<T1 extends Number> {
    T1 foo();
}

interface Child<T2 extends Integer> extends Parent<T2> {
}

Child接口扩展了Parent接口,因此我们可以用“actual”类型参数“替换”形式类型参数T1,我们可以说这是"T2 extends Integer"

interface Parent<<T2 extends Integer> extends Number>

这是唯一允许的,因为Integer是Number的子类型。 因此, Parent接口中的foo()签名(在Child接口中扩展后)简化为:

interface Parent<T2 extends Number> {
    T2 foo();
}

换句话说,签名不会改变。 Parent接口中声明的方法foo()继续返回Number作为原始类型。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM