Nic*_*ery 12 java generics polymorphism subclass covariance
我有两个看起来像这样的接口:
interface Parent<T extends Number> {
T foo();
}
interface Child<T extends Integer> extends Parent<T> {
}
Run Code Online (Sandbox Code Playgroud)
如果我有一个原始Parent
对象,则调用foo()
默认值返回a,Number
因为没有类型参数.
Parent parent = getRawParent();
Number result = parent.foo(); // the compiler knows this returns a Number
Run Code Online (Sandbox Code Playgroud)
这是有道理的.
如果我有一个原始Child
对象,我希望调用foo()
将返回一个Integer
相同的逻辑.但是,编译器声称它返回一个Number
.
Child child = getRawChild();
Integer result = child.foo(); // compiler error; foo() returns a Number, not an Integer
Run Code Online (Sandbox Code Playgroud)
我可以覆盖Parent.foo()
在Child
解决这个问题,就像这样:
interface Child<T extends Integer> extends Parent<T> {
@Override
T foo(); // compiler would now default to returning an Integer
}
Run Code Online (Sandbox Code Playgroud)
为什么会这样?有没有办法让Child.foo()
默认返回Integer
没有覆盖Parent.foo()
?
编辑:假装Integer
不是最终的.我刚刚选择Number
并Integer
作为例子,但显然它们不是最好的选择.:S
Imagine 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());
}
}
Run Code Online (Sandbox Code Playgroud)
打印:
class java.lang.Number
Run Code Online (Sandbox Code Playgroud)
该输出作为合理Java那样类型擦除,不能够保留T extends
在结果.class
文件加,因为方法foo()
是只定义了Parent
.要更改子编译器中的结果类型,需要在字节码中插入存根 Integer foo()
方法Child.class
.这是因为编译后仍然没有关于泛型类型的信息.
现在,如果你修改你的孩子:
interface Child<R extends Integer> extends Parent<R> {
@Override R foo();
}
Run Code Online (Sandbox Code Playgroud)
例如,将自己添加foo()
到Child
编译器中将使用不同但仍兼容的原型Child
在.class
文件中创建自己的方法副本Integer foo()
.现在输出是:
class java.lang.Integer
Run Code Online (Sandbox Code Playgroud)
这当然令人困惑,因为人们会期望"词汇可见性"而不是"字节码可见性".
另一种方法是编译器在两种情况下以不同的方式编译它:接口在相同的"词法范围"中,当编译器只能看到字节码时,编译器可以在不同的编译单元中看到源代码和接口.我不认为这是一个很好的选择.