Rob*_*arl 13 java generics classcastexception
我有一些看起来像这样的代码(方法的负面测试的一部分get):
import java.util.*;
public class Test {
Map<String, Object> map = new HashMap<>();
public static void main (String ... args) {
Test test = new Test();
test.put("test", "value"); // Store a String
System.out.println("Test: " + test.get("test", Double.class)); // Retrieve it as a Double
}
public <T> T get(String key, Class<T> clazz) {
return (T) map.get(key);
}
public void put(String key, Object value) {
map.put(key, value);
}
}
Run Code Online (Sandbox Code Playgroud)
我期待它抛出一个,ClassCastException但它成功打印:
Test: value
Run Code Online (Sandbox Code Playgroud)
它为什么不扔?
Cyä*_*gha 17
那是因为您正在转换为泛型类型T,它在运行时被擦除,就像所有Java泛型一样.那么在运行时实际发生的事情就是你要投射Object而不是投射Double.
请注意,例如,如果T被定义为<T extends Number>,则会转换为Number(但仍然不是Double).
如果要进行某些运行时类型检查,则需要使用实际参数clazz(在运行时可用),而不是泛型类型T(已擦除).例如,您可以执行以下操作:
public <T> T get(String key, Class<T> clazz) {
return clazz.cast(map.get(key));
}
Run Code Online (Sandbox Code Playgroud)
当在返回的"Double"上调用方法并且没有调用任何方法时,我发现字节代码有所不同.
例如,如果您在返回的"Double"上调用doubleValue()(或甚至getClass()),则会ClassCastException发生.使用javap -c Test,我得到以下字节码:
34: ldc #15 // class java/lang/Double
36: invokevirtual #16 // Method get (Ljava/lang/String;Ljava/lang/Class;)Ljava/lang/Object;
39: checkcast #15 // class java/lang/Double
42: invokevirtual #17 // Method java/lang/Double.doubleValue:()D
45: invokevirtual #18 // Method java/lang/StringBuilder.append:(D)Ljava/lang/StringBuilder;
Run Code Online (Sandbox Code Playgroud)
该checkcast操作必须被扔ClassCastException.另外,在隐含中StringBuilder,append(double)本来会被调用.
没有电话doubleValue()(或getClass()):
34: ldc #15 // class java/lang/Double
36: invokevirtual #16 // Method get:(Ljava/lang/String;Ljava/lang/Class;)Ljava/lang/Object;
39: invokevirtual #17 // Method java/lang/StringBuilder.append (Ljava/lang/Object;)Ljava/lang/StringBuilder;
Run Code Online (Sandbox Code Playgroud)
有没有checkcast操作,而append(Object)被称为上隐StringBuilder,因为类型擦除后,T只是Object无论如何.
你没有得到a,ClassCastException因为你从地图返回值的上下文不需要编译器在这一点上执行检查,因为它等同于将值赋给类型的变量Object(参见rgettman的答案).
致电:
test.get("test", Double.class);
Run Code Online (Sandbox Code Playgroud)
是使用+运算符的字符串连接操作的一部分.从地图返回的对象只是被视为一个对象Object.为了显示返回的'对象'作为String对toString()方法的调用是必需的,因为这是一个Object不需要强制转换的方法.
如果你把调用带到test.get("test", Double.class);字符串连接的上下文之外,你会发现它不起作用,即
这不编译:
// can't assign a Double to a variable of type String...
String val = test.get("test", Double.class);
Run Code Online (Sandbox Code Playgroud)
但这样做:
String val = test.get("test", Double.class).toString();
Run Code Online (Sandbox Code Playgroud)
换句话说,你的代码:
System.out.println("Test: " + test.get("test", Double.class));
Run Code Online (Sandbox Code Playgroud)
相当于:
Object obj = test.get("test", Double.class);
System.out.println("Test: " + obj);
Run Code Online (Sandbox Code Playgroud)
要么:
Object obj = test.get("test", Double.class);
String value = obj.toString();
System.out.println("Test: " + value);
Run Code Online (Sandbox Code Playgroud)
考虑删除类型参数(类型擦除)后类的样子是有启发性的:
public class Test {
Map map = new HashMap();
public static void main (String ... args) {
Test test = new Test();
test.put("test", "value");
System.out.println("Test: " + test.get("test", Double.class));
}
public Object get(String key, Class clazz) {
return map.get(key);
}
public void put(String key, Object value) {
map.put(key, value);
}
}
Run Code Online (Sandbox Code Playgroud)
这会编译并产生与您看到的相同的结果。
棘手的部分是这一行:
System.out.println("Test: " + test.get("test", Double.class));
Run Code Online (Sandbox Code Playgroud)
如果你这样做了:
Double foo = test.get("test", Double.class);
Run Code Online (Sandbox Code Playgroud)
那么在类型擦除之后,编译器将插入一个强制转换(因为在类型擦除之后test.get()返回Object):
Double foo = (Double)test.get("test", Double.class);
Run Code Online (Sandbox Code Playgroud)
类似地,编译器也可以在上面的行中插入强制转换,如下所示:
System.out.println("Test: " + (Double)test.get("test", Double.class));
Run Code Online (Sandbox Code Playgroud)
但是,它不会插入强制转换,因为强制转换对于编译和正确运行来说并不是必需的,因为字符串连接 ( +) 以相同的方式作用于所有对象;它只需要知道类型是Object,而不是特定的子类。因此,编译器可以省略不必要的强制转换,在本例中确实如此。
| 归档时间: |
|
| 查看次数: |
1772 次 |
| 最近记录: |