java为什么要编译这个涉及通用接口方法的程序?
下面的代码在运行时抛出一个ClassCastException
,行public String foo() { return "bar"; }
生成一个警告“find'java.lang.String',required'T”。我理解ClassCastException
(调用接口方法时T
等于Integer
,但foo
返回一个String
),我理解警告(它正试图警告我们这个问题)。但我不明白的是为什么程序会编译。为什么允许返回String
的方法重写返回T
的方法
public class Main {
interface MyInterface {
<T> T foo();
}
static class MyClass implements MyInterface {
@Override
public String foo() { return "bar"; }
}
public static void main(String[] args) {
int a = ((MyInterface) new MyClass()).<Integer>foo();
}
}
# 1 楼答案
这是一个令人惊讶的深刻问题。Java语言规范writes:
在类
C
中声明或由类C
继承的实例方法mC
重写在类A
中声明的另一个方法mA
,如果以下所有条件均为真:mC
的签名是mA
签名的子签名(§8.4.2)李>and:
在我们的例子中显然不是这样,因为
MyInterface.foo
声明了一个类型参数,但MyClass.foo
没有规范解释了第二个条件的必要性,如下所示:
事实上,第二个条件在我们的例子中是满足的,因为
MyClass.foo
有签名foo()
,这也是对MyInterface.foo
签名的擦除这就剩下了不同返回类型的问题。规范writes:
and:
在我们的例子中,R1=String和R2=T。因此,第一个条件为false,因为String不是T的子类型。但是,可以通过未选中的转换将String转换为T,从而使第二个条件为true
规范解释了第二和第三个条件的必要性,如下所示:
也就是说,编译器会接受您的代码,因为您无意中使用了两个编译器特性,通过允许对现有代码进行逐步泛型,从而简化了到泛型的转换。这些特性在编译时类型系统中打开了一个循环孔,这可能会导致堆污染和奇怪的ClassCastException,这些行甚至可能没有源代码中的强制转换。为了提醒您注意这种危险,编译器需要发出未经检查的警告。因此,这些特性应仅用于其预期目的(与非通用遗留代码兼容),否则应避免使用
# 2 楼答案
当幼稚地声明
<T> T foo();
时,编译器将尝试从将被赋值的变量中推断foo
的结果类型。这就是为什么要编译它。它可以很容易地进行测试:编译时的结果不能是
Object
。如果它是对象,则必须添加手动类型转换,但实际情况并非如此简而言之,声明这样的方法是无用的,只会给程序员带来混乱和混乱
此方法声明在以下情况之一中非常有用:
在接口/类的顶层声明泛型
<T>
时:将
Class<T>
作为参数传递时,这使编译器能够推断结果类型,并在不满足此条件时引发适当的编译器错误:# 3 楼答案
因为声明方法
<T> T foo()
与声明它Object foo()
基本相同。如果您在某个地方与类型参数有其他连接(可能接口是在T
上参数化的,而方法只是T foo()
),那么可能会有某种链接被破坏。然而,在本例中,您只是回到了标准规则,即重写可以返回超类型返回的任何更具体的子类型# 4 楼答案
由于类型擦除,
T
部分只是字节码中的一个Object
。您可以返回更具体的类型,在本例中为String