Java 泛型 "upcast" 到未参数化类型
Java Generics "upcast" to unparameterized type
为什么我只在取消注释 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());
}
}
提前致谢。
根据 Java Docs,对于 Object
class
中的 getClass()
方法
实际结果类型为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。这个答案只是解释了失败背后的原因和解决方法。
当您编写一系列方法调用时:
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
为什么我只在取消注释 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());
}
}
提前致谢。
根据 Java Docs,对于 Object
class
getClass()
方法
实际结果类型为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。这个答案只是解释了失败背后的原因和解决方法。
当您编写一系列方法调用时:
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