[英]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
不是最终的。 我只选择了Number
和Integer
作为例子,但显然它们不是最好的选择。 :S
想象一下public interface Parent<T extends Number>
是在另一个编译单元中定义的 - 在单独的文件Parent.java
。
然后,在编译Child
和main
,编译器会将方法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.