为什么只有在Main.main()中取消注释第三个语句时才会出现ClassCastException?并没有例外,但执行良好的第一和第二声明?
public class Tuple<K, V> {
public final K first;
public final V second;
public Tuple(K first, V second) {
this.first = first;
this.second = second;
}
@Override public String toString() {
return "Tuple{" + "first = " + first + ", second = " + second + '}';
}
}
class Test { static Tuple f(){return new Tuple("test", 8);} }
class Bar {}
class Main{
public static void main(String[] args) {
Tuple<String, Bar> t = Test.f();
System.out.println(t);
//System.out.println(t.second.getClass().getSimpleName());
}
}
提前致谢。
答案 0 :(得分:3)
当你编写一系列方法调用时:
System.out.println(t.second.getClass().getSimpleName());
编译器有效地将其扩展为:
TypeOfTSecond tmpTSecond = t.second;
Class<?> clazzTmp = tmp.getClass();
String nameTmp = clazzTmp.getSimpleName();
System.out.println(nameTmp);
现在,如果发生t.second
是泛型类型,编译器会将强制转换插入t.second
所需的类型:
Bar tmpTSecond = (Bar) t.second;
即使您永远不会访问任何Bar
特定功能,您也会获得ClassCastException
。
为了证明这一点,这里是字节码:
public static void main(java.lang.String[]);
Code:
0: invokestatic #2 // Method Test.f:()LTuple;
3: astore_1
4: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream;
7: aload_1
8: getfield #4 // Field Tuple.second:Ljava/lang/Object;
11: checkcast #5 // class Bar
14: invokevirtual #6 // Method java/lang/Object.getClass:()Ljava/lang/Class;
17: invokevirtual #7 // Method java/lang/Class.getSimpleName:()Ljava/lang/String;
20: invokevirtual #8 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
23: return
第8行是将t.second
推入堆栈的地方;第11行是Bar
的演员阵容。
这只是因为声明test.f()
时使用的原始类型:
static Tuple f(){return new Tuple("test", 8);}
如果这被正确声明为
static Tuple<String, Integer> f(){return new Tuple<>("test", 8);}
然后这一行
Tuple<String, Bar> t = Test.f();
不会编译。但是使用原始类型会禁用编译器的类型检查,因此无法保证阻止此类运行时错误。
主要的外卖课程是never use raw types。
中学课程是关注你的编译器(或IDE)的警告。编译这段代码时,我被告知:
Note: Main.java uses unchecked or unsafe operations.
Note: Recompile with -Xlint:unchecked for details.
当我用那个标志重新编译时:
Main.java:19: warning: [unchecked] unchecked call to Tuple(K,V) as a member of the raw type Tuple
return new Tuple("test", 8);
^
where K,V are type-variables:
K extends Object declared in class Tuple
V extends Object declared in class Tuple
Main.java:26: warning: [unchecked] unchecked conversion
Tuple<String, Bar> t = Test.f();
^
required: Tuple<String,Bar>
found: Tuple
2 warnings
答案 1 :(得分:0)
根据Java Docs,getClass()
类
Object
方法
实际结果类型为 Class<? extends |X|>
,其中 | X | 是擦除调用getClass的表达式的静态类型。
在声明部分中,类型为Bar
Tuple<String, Bar> t = Test1.f();
由于Integer
和Bar
之间没有父子关系,因此在尝试将Integer转换为Bar时,会得到ClassCastException(即Integer extends Bar
不正确)
这是修复
Tuple<String, Object> t = Test1.f();
注意: It is not encouraged to use raw types。这个答案只是解释了失败背后的原因和解决方法。