如果我在System.out.println()中直接使用t.getTheString(implClass.class)的返回对象,则代码会抛出java.lang.ClassCastException。虽然当我将对象分配给某个引用时,它工作正常,为什么?
interface testInterface{
public String testMethod();
}
class testClass{
public <T extends testInterface> T getTheString(Class< ? extends testInterface> classType){
try {
testInterface obj = classType.newInstance();
return (T)obj;
} catch (InstantiationException | IllegalAccessException e) {
e.printStackTrace();
}
return null;
}
}
class implClass implements testInterface{
@Override
public String testMethod() {
return "Sample String";
}
@Override
public String toString(){
return super.toString() + " Overridden toString()";
}
}
public class genericTest {
public static void main(String args[]){
testClass t = new testClass();
testInterface ti = t.getTheString(implClass.class);
implClass i = t.getTheString(implClass.class);
//System.out.println(i); //<= Works fine
//System.out.println(ti); //<= Works fine
System.out.println(t.getTheString(implClass.class)); // Exception in thread "main" java.lang.ClassCastException: implClass cannot be cast to java.lang.String
}
}
编辑:很多人都对这个问题感到困惑。使用上限只是为了创建这个例子。我知道这是不正确的。 令人怀疑的是,如果您对上面的代码进行反编译,您将看到编译器将t.getTheString(implClass.class)结果类型转换为String,因为它可能不确定该类型。我的问题是,如果它是对象的类型转换,它会更好。在这种情况下,它将调用toString()方法,代码将正常工作。 这是Java编译器的问题还是我在这里遗漏了什么?
答案 0 :(得分:2)
问题的原因已在其他答案中提及 - 您的classType
参数可以是实现testInterface
的任何类型,不一定是T
。
这是一个解决方案。
目前的问题是classType
没有受到约束,如果没有足够的上下文,T
无法正确推断,导致obj
被转换为错误的类型。因此,如果我们将方法更改为:
public <T extends testInterface> T getTheString(Class<T> classType){
每次都可以从参数中正确推断 T
!
另一种解决方案是使用您想要的类型对编译器进行反馈:
System.out.println((testInterface)t.getTheString(implClass.class));
从您的评论到另一个答案:
如果编译器不确定类型,为什么不将其类型转换为Object 而不是字符串
只有两个println
重载,编译器可以选择调用,因为其他是原始类型。它可以是println(Object)
或println(String)
。根据JLS(第15.12.2节):
此步骤使用方法的名称和参数表达式的类型来定位可访问和适用的方法可能有多个这样的方法,在这种情况下,选择最具体的方法。< / em>的
有关详细信息,请参阅this post。
由于String比Object更具体,因此选择后者。
答案 1 :(得分:1)
泛型在运行时被删除,所以当JVM需要选择调用它的System.out.println()
变体时,它不知道它是什么意思并且假设传入的类是String
并且需要{ {1}}显然您的System.out.println(String string)
实例不是implClass
。
这一切都来自你使用泛型,但没有在类级别声明它们。更改String
声明以声明您的通用testClass
T
一旦你这样做,编译器应该在尝试将
class testClass<T> {
转换为testInterface
时开始抱怨丢失强制转换
implClass
一旦你纠正它像:
implClass i = t.getTheString(implClass.class);
implClass i = (implClass) t.getTheString(implClass.class);
将按预期开始工作。
如果您想知道为什么编译器选择错误的方法,请考虑以下事项:
- 类型T没有在类级别声明,所以当它在编译期间被删除时,它变成“任何东西”,
- 当编译器稍后试图找出要调用的System.out.println()
方法的匹配变量时,首先匹配,因为类型已变为“任何”,第一个方法变量将匹配
- 在PrintStream的Oracle JDK impl中,第一个impl恰好是String作为参数的那个(检查源代码;)
答案 2 :(得分:0)
适合我;重命名并运行为Main.java:
> javac Main.java
Note: Main.java uses unchecked or unsafe operations.
Note: Recompile with -Xlint:unchecked for details.
> java Main
implClass@15dc8287 Overridden toString()
然后:
> javac -Xlint:unchecked Main.java
Main.java:10: warning: [unchecked] unchecked cast
return (T)obj;
^
required: T
found: testInterface
where T is a type-variable:
T extends testInterface declared in method <T>getTheString(Class<? extends testInterface>)
这里的问题:在你的方法getTheString()中你转换为T;但无保证传入的类与该T有任何关系!实现该接口的类不一定也是T。
你所看到的例外情况;如上所述 - 不可重现(你身边有一些设置问题)?
答案 3 :(得分:0)
如果您取消注释两个“it works”行并通过反编译器运行genericTest.class(我使用http://www.javadecompilers.com/),您将看到:
public static void main(String[] arrstring) {
testClass testClass2 = new testClass();
Object t = testClass2.getTheString(implClass.class);
implClass implClass2 = (implClass)testClass2.getTheString(implClass.class);
System.out.println((Object)implClass2);
System.out.println(t);
System.out.println((String)testClass2.getTheString(implClass.class));
}
前两次调用println
调用接受Object的方法,而Object又调用toString()
并打印出您期望的结果。在第三个println
中,编译器期望结果真的是一个String,并尝试转换,这是你从中获取异常的地方。