具有泛型子类化的Java自动返回类型协方差
我有两个界面如下所示:具有泛型子类化的Java自动返回类型协方差,java,generics,polymorphism,subclass,covariance,Java,Generics,Polymorphism,Subclass,Covariance,我有两个界面如下所示: interface Parent<T extends Number> { T foo(); } interface Child<T extends Integer> extends Parent<T> { } 这是有道理的 如果我有一个原始的子对象对象,那么调用foo()将按照相同的逻辑返回一个整数。但是,编译器声明它返回一个数字 Child child = getRawChild(); Integer result = c
interface Parent<T extends Number> {
T foo();
}
interface Child<T extends Integer> extends Parent<T> {
}
这是有道理的
如果我有一个原始的子对象
对象,那么调用foo()
将按照相同的逻辑返回一个整数
。但是,编译器声明它返回一个数字
Child child = getRawChild();
Integer result = child.foo(); // compiler error; foo() returns a Number, not an Integer
我可以覆盖Child
中的Parent.foo()
interface Child<T extends Integer> extends Parent<T> {
@Override
T foo(); // compiler would now default to returning an Integer
}
接口子级扩展父级{
@凌驾
T foo();//编译器现在默认返回整数
}
为什么会发生这种情况?有没有办法让Child.foo()
默认返回一个整数
,而不重写Parent.foo()
编辑:假装Integer
不是最终值。我刚刚选择了Number
和Integer
作为示例,但显然它们不是最佳选择:S这两个T
S并不完全相同。假设接口的定义是这样的:
interface Parent<T1 extends Number> {
T1 foo();
}
interface Child<T2 extends Integer> extends Parent<T2> {
}
这是唯一允许的,因为Integer是Number的子类型。因此,父界面中的foo()
签名(在子界面中扩展后)简化为:
interface Parent<T2 extends Number> {
T2 foo();
}
接口父级{
t2foo();
}
换句话说,签名没有改变。在Parent
接口中声明的方法foo()
继续将Number
作为原始类型返回
这是基于@AdamGent的思想
不幸的是,我对JLS不够流利,无法从规范中证明以下内容
想象一下,公共接口父级
是在一个不同的编译单元中定义的,位于一个单独的文件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());
}
}
此输出是合理的,因为java不执行类型擦除,并且不能在结果中保留T extends
。class
文件plus,因为方法foo()
仅在父级中定义。要更改子编译器中的结果类型,需要在child.class
字节码中插入存根Integer foo()
方法。这是因为编译后没有关于泛型类型的信息
现在,如果您将您的孩子修改为:
interface Child<R extends Integer> extends Parent<R> {
@Override R foo();
}
这当然令人困惑,因为人们期望“词汇可见性”而不是“字节码可见性”
另一种方法是编译器在两种情况下以不同的方式编译:在同一“词法范围”中的接口,编译器可以看到源代码;在另一个编译单元中的接口,编译器只能看到字节码。我不认为这是一个好的选择。我认为这是因为没有孩子的覆盖,孩子继承了父母的foo
方法,根据父母的说法,t
是一个数字。要使父对象中的foo
返回一个Integer
,它需要知道有关其子类的信息,这类子类破坏了层次结构…请尝试接口子对象扩展父对象
@fukanchik,它甚至不是有效的Java。@AdamGent谢谢您,它编译失败了。我浏览了JLS第4章。类型、值和变量
,找不到此处不允许使用上限的确切位置。@fukanchik它是一个。签名没有更改,但有一些语言可以进行泛型扩展(即复制/创建子类中的方法)。这与Java如何实现泛型有关。在Java语言中实现泛型类型的方式肯定有一些特殊性(Java在这方面不是唯一的),但通常可以预测并使用这些类型。我同意,但我可以理解OPs的困惑。我删除了我的答案,以支持你的答案。我不确定我的为什么会被否决(可能是因为我不明白OP想知道Java为什么选择这样实现泛型)。如果投票人只是加了一句话,那会有帮助的。但是你的答案和@Scottb更好。
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
interface Child<R extends Integer> extends Parent<R> {
@Override R foo();
}
class java.lang.Integer