在Java中转换为generics类型不会引发ClassCastException?

我遇到了一个奇怪的Java行为,看起来像个bug。 是吗? 即使对象不是K的实例,将Object转换为generics类型(例如, K )也不会抛出ClassCastException 。 这是一个例子:

 import java.util.*; public final class Test { private static void addToMap(Map map, Object ... vals) { for(int i = 0; i < vals.length; i += 2) map.put((K)vals[i], (V)vals[i+1]); //Never throws ClassCastException! } public static void main(String[] args) { Map m = new HashMap(); addToMap(m, "hello", "world"); //No exception System.out.println(m.get("hello")); //Prints "world", which is NOT an Integer!! } } 

更新 :感谢cletus和Andrzej Doyle提供的有用答案。 因为我只能接受一个,所以我接受了Andrzej Doyle的回答,因为它让我得到了一个我认为并不太糟糕的解决方案。 我认为这是在单行中初始化小地图的一种更好的方法。

  /** * Creates a map with given keys/values. * * @param keysVals Must be a list of alternating key, value, key, value, etc. * @throws ClassCastException if provided keys/values are not the proper class. * @throws IllegalArgumentException if keysVals has odd length (more keys than values). */ public static Map build(Class keyClass, Class valClass, Object ... keysVals) { if(keysVals.length % 2 != 0) throw new IllegalArgumentException("Number of keys is greater than number of values."); Map map = new HashMap(); for(int i = 0; i < keysVals.length; i += 2) map.put(keyClass.cast(keysVals[i]), valClass.cast(keysVals[i+1])); return map; } 

然后你这样称呼它:

 Map m = MapBuilder.build(String.class, Number.class, "L", 11, "W", 17, "H", 0.001); 

正如cletus所说,擦除意味着你无法在运行时检查这一点(并且由于你的转换,你无法在编译时检查这一点)。

请记住,generics是仅编译时function。 集合对象没有任何通用参数,只有您为该对象创建的引用 。 这就是为什么如果你需要从原始类型甚至Object转发一个集合,你会得到很多关于“unchecked cast”的警告 – 因为编译器无法validation该对象是否是正确的generics类型(如对象本身没有generics类型)。

另外,请记住铸造意味着什么 – 它是告诉编译器的一种方式“我知道你不一定能检查类型匹配,但相信我 ,我知道他们这样做”。 当你覆盖类型检查(错误)然后最终导致类型不匹配时,谁会责备? 😉

看起来您的问题在于缺乏异构的通用数据结构。 我建议您的方法的类型签名应该更像private static void addToMap(Map map, List> vals) ,但我不相信这真的让你有所收获。 对列表基本上是一个映射,因此为了调用方法而构造typesafe vals参数将与直接填充映射一样多。

如果你真的,真的想保持你的课程大致原样,但添加运行时类型安全,也许以下将给你一些想法:

 private static void addToMap(Map map, Object ... vals, Class keyClass, Class valueClass) { for(int i = 0; i < vals.length; i += 2) { if (!keyClass.isAssignableFrom(vals[i])) { throw new ClassCastException("wrong key type: " + vals[i].getClass()); } if (!valueClass.isAssignableFrom(vals[i+1])) { throw new ClassCastException("wrong value type: " + vals[i+1].getClass()); } map.put((K)vals[i], (V)vals[i+1]); //Never throws ClassCastException! } } 

Javagenerics使用类型擦除,这意味着那些参数化类型不会在运行时保留,因此这是完全合法的:

 List list = new ArrayList(); list.put("abcd"); List list2 = (List)list; list2.add(3); 

因为编译的字节码看起来更像是这样的:

 List list = new ArrayList(); list.put("abcd"); List list2 = list; list2.add(3); // auto-boxed to new Integer(3) 

Javagenerics只是构造Object的语法糖。

Java的generics是使用“类型擦除”完成的,这意味着在运行时,代码不知道你有Map – 它只是看到一个Map。 因为你正在将这些东西转换为Objects(通过你的addToMap函数的参数列表),所以在编译时,代码“看起来正确”。 它在编译时不会尝试运行这些东西。

如果您在编译时关心类型,请不要将它们称为Object。 :)使你的addToMap函数看起来像

 private static void addToMap(Map map, K key, V value) { 

如果要在地图中插入多个项目,则需要创建类似java.util的Map.Entry类,并将键/值对包装在该类的实例中。

这是对Generics在Java中做什么和不做什么的一个相当好的解释: http : //www.angelikalanger.com/GenericsFAQ/FAQSections/ParameterizedTypes.html

它与C#非常不同!

我想你是想尝试做这样的事情? 您要添加到地图的对的编译时安全性:

 addToMap(new HashMap(), new Entry("FOO", 2), new Entry("BAR", 8)); public static void addToMap(Map map, Entry... entries) { for (Entry entry: entries) { map.put(entry.getKey(), entry.getValue()); } } public static class Entry { private K key; private V value; public Entry(K key,V value) { this.key = key; this.value = value; } public K getKey() { return key; } public V getValue() { return value; } } 

评论后编辑:

啊,那么也许你真正想要的就是用这种神秘的语法来骚扰那些刚转向Java的新员工。

 Map map = new HashMap() {{ put("Foo", 1); put("Bar", 2); }}; 

Javagenerics仅适用于编译期间而非运行时。 问题是你实现的方式,java编译器没有机会在编译时确保类型安全。

由于你的K,V从不说它们扩展任何特定的类,在编译期间java无法知道它应该是一个整数。

如果您更改代码如下

private static void addToMap(Map map,Object … vals)

它会给你一个编译时错误