【发布时间】:2013-05-17 14:04:49
【问题描述】:
给出以下代码:
public static void main(String[] args) {
HashMap<String, String> hashMap = new HashMap<>();
HashMap<String, Object> dataMap = new HashMap<>();
dataMap.put("longvalue", 5L);
class TestMethodHolder {
<T> T getValue(Map<String, Object> dataMap, String value) {
return (T)dataMap.get(value);
}
}
hashMap.put("test", new TestMethodHolder().<String>getValue(dataMap, "longvalue"));
String value = hashMap.get("test"); // ClassCastException occurs HERE
System.out.println(value);
}
这段代码编译对我来说并不奇怪,而是 ClassCastException 发生在 get 行而不是它上面的 put 行,尽管我对可能发生的事情有一个有根据的猜测。由于泛型类型在运行时被擦除,因此 getValue() 中的转换实际上从未在运行时发生,并且实际上是对 Object 的转换。如果该方法将按如下方式实现,则运行时转换将发生并且它将在 put 行上失败(如预期的那样)。谁能证实这一点?
class TestMethodHolder {
String getValue(Map<String, Object> dataMap, String value) {
return (String)dataMap.get(value);
}
}
这是使用泛型的已知缺陷或奇怪之处吗?那么在调用方法时使用 表示法是不好的做法吗?
编辑: 我使用的是默认的 Oracle JDK 1.7_03。
上面的另一个隐含问题:原始 getValue 中的转换是否仍然在运行时发生,但转换实际上是针对 Object 的 - 或者编译器是否足够聪明,可以完全避免在运行时发生这种转换?这可能解释了人们在运行 ClassCastException 时注意到的发生位置的差异。
【问题讨论】:
-
它编译并运行,因为类型擦除会在运行时使其成为
Map<Object, Object>。当然,您会得到一个ClassCastException,因为您将Long视为String,这是错误的。 -
@LuiggiMendoza 我认为 OP 知道这一点 - 请参阅他的“有根据的猜测”部分。
-
您在
return (T)dataMap.get(value);中有一个未经检查的从 Object 到 T 的强制转换。你在这里推翻了编译器。 -
@ZiyaoWei 我猜这个例子不是最正确的,因为OP使用
Object并且任何对象引用都可以作为Object。此外,在运行时,代码将作为hashMap.put("test", someObject)执行,这里没有强类型,因为类型擦除会将其视为HashMap<Object, Object>,在尝试将其检索为String时显然会失败,因为它的真实类型是Long. -
我当然不会称其为“缺陷”......显然这应该被认为是不安全的。