generics奇怪 – 我可以在Map 中插入一个Long值,它会在运行时编译并且不会失败

提供以下代码:

public static void main(String[] args) { HashMap hashMap = new HashMap(); HashMap dataMap = new HashMap(); dataMap.put("longvalue", 5L); class TestMethodHolder {  T getValue(Map dataMap, String value) { return (T)dataMap.get(value); } } hashMap.put("test", new TestMethodHolder().getValue(dataMap, "longvalue")); String value = hashMap.get("test"); // ClassCastException occurs HERE System.out.println(value); } 

对我来说这个代码编译并不奇怪,而是ClassCastException出现在get行上而不是它上面的put行,尽管我确实有一个有根据的猜测,可能会发生什么。 由于generics类型在运行时被擦除,因此getValue()中的强制转换实际上从未在运行时发生,并且实际上是对Object的强制转换。 如果该方法将在下面实现如下,那么将发生运行时转换,并且它将在put行上失败(如预期的那样)。 谁能证实这一点?

 class TestMethodHolder { String getValue(Map dataMap, String value) { return (String)dataMap.get(value); } } 

这是使用generics的已知缺陷还是奇怪? 那么在调用方法时使用表示法是不好的做法吗?

编辑:我使用默认的Oracle JDK 1.7_03。

上面提到的另一个隐含问题:原始getValue STILL中的强制转换是在运行时发生但是强制转换实际上是对象 – 或者编译器是否足够聪明,以便在运行时不会发生此强制转换? 这可能解释了人们在运行时注意到ClassCastException发生的位置的差异。

编译器依赖于类型安全来进行假设并进行转换/优化。 不幸的是,类型安全性可以通过未经检查的强制转换来破坏。 如果您的程序包含不正确的未经检查的强制转换,则不清楚编译器应该做什么。 理想情况下,它应该在未经检查的强制转换的确切点进行运行时检查,在您的示例中,当Object被转换为T 。 但这是不可能的,因为擦除,这不是类型系统的一部分。

在你的例子中的其他地方,类型都是合理的,因此编译器可以假设getValue()确实返回一个String ,不需要仔细检查。 但是,像Eclipse编译器那样进行检查也是合法的(可能是因为它将返回值赋给String本地临时变量)。

所以坏消息是,如果您的程序包含不正确的未经检查的强制转换,则其行为未定义….因此,请通过严格的推理确保所有未经检查的强制转换都是正确的。

一个好的做法是检查所有未经检查的强制转换,以便合法地抑制未经检查的警告。 例如

   T getValue(Map dataMap, String value, Class type) { Object value = dataMap.get(value); if(value!=null && !type.isInstance(value)) // check! throw new ClassCastException(); @SuppressWarning("unchecked") T t = (T)value; // this is safe, because we've just checked return t; } 

请参阅我对类似问题的回答: 使用Java进行惰性类转换?

线

 return (T)dataMap.get(value); 

生成一个未经检查的强制转换警告,并且根据规范,任何此类警告的存在都会使您的代码类型不安全。 ClassCastException是在您第一次尝试将类型不安全的结果分配给错误类型的变量时发生的,因为这是第一次编译的代码进行类型检查。

请注意,Eclipse的编译器会插入比JLS强制要求更多的类型检查,因此,如果在Eclipse中编译,则hashMap.put调用将失败并显示CCE 。 编译器知道此调用必须具有两个String参数,因此可以在实际方法调用之前插入类型检查。

正如您猜测的那样,如果您使用特定的String替换genericsT ,那么类型检查将在该点发生 – 并且失败。

在编译期间会删除此类型信息(请参阅Neal Gafter的“Reified Generics for Java” )。

实际上,您可以使用Collections实用程序方法保护集合:

 Class type = String.class; Map hashMap = new HashMap<>(); Map map = Collections.checkedMap(hashMap, type, type); Map rawType = map; // pre-Java 1.5 code knows nothing about generics rawType.put(1, 2); // throws ClassCastException at runtime