Java 为什么这个程序需要编译一个通用接口方法?
下面的代码在运行时抛出一个Java 为什么这个程序需要编译一个通用接口方法?,java,generics,interface,classcastexception,Java,Generics,Interface,Classcastexception,下面的代码在运行时抛出一个ClassCastException,行public String foo(){return“bar”}生成一个警告“find'java.lang.String',required'T'”。我理解ClassCastException(调用接口方法时T等于Integer,但foo返回一个字符串),我理解警告(它正试图警告我们这个问题)。但我不明白的是为什么程序会编译。为什么允许返回字符串的方法重写返回T的方法 public class Main { interf
ClassCastException
,行public String foo(){return“bar”}
生成一个警告“find'java.lang.String',required'T'”。我理解ClassCastException
(调用接口方法时T
等于Integer
,但foo
返回一个字符串
),我理解警告(它正试图警告我们这个问题)。但我不明白的是为什么程序会编译。为什么允许返回字符串的方法重写返回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();
}
}
公共类主{
接口MyInterface{
T foo();
}
静态类MyClass实现MyInterface{
@凌驾
公共字符串foo(){return“bar”;}
}
公共静态void main(字符串[]args){
int a=((MyInterface)new MyClass()).foo();
}
}
因为声明一个方法T foo()
与声明它Object foo()
基本相同。如果您在某个地方与type参数有其他连接(可能接口是在T
上参数化的,而方法只是T foo()
),那么可能会有某种链接被破坏。然而,在这种情况下,您只是回到了标准规则,即覆盖可以返回超类型返回的任何更具体的子类型。由于类型擦除,T
部分只是字节码中的一个对象。您可以返回更具体的类型,在本例中,当天真地声明tfoo()时,返回String代码>编译器将尝试从将被赋值的变量推断foo
的结果类型。这就是为什么要编译它。它可以很容易地进行测试:
interface MyInterface {
<T> T foo();
}
class MyClass implements MyInterface {
@Override
public String foo() { return "bar"; }
}
public class Main {
public static void main(String[] args) {
MyInterface myInterface = new MyClass();
//the result of foo will be String
String bar = myInterface.foo();
System.out.println(bar); //prints "bar"
try {
//the result of foo at compile time will be Integer
Integer fail = myInterface.foo();
System.out.println(fail); //won't be executed
} catch (ClassCastException e) {
//for test purposes only. Exceptions should be managed better
System.out.println(e.getMessage()); //prints "java.lang.String cannot be cast to java.lang.Integer"
}
}
}
将类
作为参数传递时,这使编译器能够推断结果类型,并在不满足此条件时引发适当的编译器错误:
interface MyInterface {
<T> T foo(Class<T> clazz);
}
class MyClass implements MyInterface {
@Override
public <T> T foo(Class<T> clazz) {
try {
return clazz.newInstance();
} catch (InstantiationException e) {
e.printStackTrace(System.out);
} catch (IllegalAccessException e) {
e.printStackTrace(System.out);
}
return null;
}
}
public class Main {
public static void main(String[] args) {
MyInterface myInterface = new MyClass();
//uncomment line below to see the compiler error
//Integer bar = myInterface.foo(String.class);
//line below compiles and runs with no problem
String bar = myInterface.foo(String.class);
System.out.println(bar);
}
}
接口MyInterface{
T-foo(课堂);
}
类MyClass实现MyInterface{
@凌驾
公共T-foo(课堂){
试一试{
返回clazz.newInstance();
}捕获(实例化异常e){
e、 printStackTrace(系统输出);
}捕获(非法访问例外e){
e、 printStackTrace(系统输出);
}
返回null;
}
}
公共班机{
公共静态void main(字符串[]args){
MyInterface MyInterface=新的MyClass();
//取消注释下面的行以查看编译器错误
//整型条=myInterface.foo(String.class);
//下一行编译并运行没有问题
stringbar=myInterface.foo(String.class);
系统输出打印项次(巴);
}
}
这是一个令人惊讶的深刻问题。Java语言规范:
在类
C
中声明或由类C
继承的一个实例方法mC
,重写了类A
中声明的另一个方法mA
,如果以下所有条件均为真:
mC
的签名是mA
签名的子签名(§8.4.2)
:
两种方法或构造函数,M
和N
,如果它们具有相同的名称、相同的类型参数(如有)(§8.4.4),则具有相同的签名,并且在将N
的形式参数类型调整为M
的类型参数后,具有相同的形式参数类型
在我们的例子中显然不是这样,因为MyInterface.foo
声明了一个类型参数,但MyClass.foo
没有声明
方法m1
的签名是方法m2
签名的子签名,如果:
m2
具有与m1
相同的签名,或
m1
的签名与m2
签名的擦除(§4.6)相同
规范解释了第二个条件的必要性,如下所示:
子签名的概念旨在表示两个方法之间的关系,这两个方法的签名不完全相同,但其中一个方法可以覆盖另一个方法。具体来说,它允许签名不使用泛型类型的方法重写该方法的任何泛型版本。这一点很重要,这样库设计人员就可以独立于定义库的子类或子接口的客户端自由地生成方法
事实上,第二个条件在我们的例子中得到满足,因为MyClass.foo
具有签名foo()
,这也是对MyInterface.foo
签名的擦除
这就剩下了不同返回类型的问题。规范:
如果返回类型为R1的方法声明d1
覆盖或隐藏另一个返回类型为R2的方法声明d2
,则d1
必须是返回类型可替换(§8.4.5)的d2
,否则会发生编译时错误
:
如果满足以下任一条件,则返回类型为R1的方法声明d1为返回类型,可替换为另一个返回类型为R2的方法d2:
-
- 如果R1是引用类型,则以下情况之一为真:
- R1适用于d2的类型参数(§8.4.4),是R2的一个子类型
- R1可以通过未经检查的转换(§5.1.9)转换为R2的子类型
- d1与d2没有相同的签名(§8.4.2),R1=| R2 |
在我们的例子中,R1=String和R2=T。因此,第一个条件为false,因为String不是T的子类型。但是,可以通过未选中的转换将String转换为T,从而使第二个条件为true
规格说明
interface MyInterface {
<T> T foo(Class<T> clazz);
}
class MyClass implements MyInterface {
@Override
public <T> T foo(Class<T> clazz) {
try {
return clazz.newInstance();
} catch (InstantiationException e) {
e.printStackTrace(System.out);
} catch (IllegalAccessException e) {
e.printStackTrace(System.out);
}
return null;
}
}
public class Main {
public static void main(String[] args) {
MyInterface myInterface = new MyClass();
//uncomment line below to see the compiler error
//Integer bar = myInterface.foo(String.class);
//line below compiles and runs with no problem
String bar = myInterface.foo(String.class);
System.out.println(bar);
}
}