繁体   English   中英

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

[英]Casting to generic type in Java doesn't raise ClassCastException?

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

import java.util.*;
public final class Test {
  private static<K,V> void addToMap(Map<K,V> 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<String,Integer> m = new HashMap<String,Integer>();
    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<K,V> Map<K,V> build(Class<K> keyClass, Class<V> valClass, Object ... keysVals)
  {
    if(keysVals.length % 2 != 0)
      throw new IllegalArgumentException("Number of keys is greater than number of values.");

    Map<K,V> map = new HashMap<K,V>();
    for(int i = 0; i < keysVals.length; i += 2)
      map.put(keyClass.cast(keysVals[i]), valClass.cast(keysVals[i+1]));

    return map;
  }

然后你这样称呼它:

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

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

List<String> list = new ArrayList<String>();
list.put("abcd");
List<Integer> list2 = (List<Integer>)list;
list2.add(3);

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

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

Java泛型只是构建Object的语法糖。

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

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

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

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

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

private static<K,V> void addToMap(Map<K,V> map, Object ... vals, Class<K> keyClass, Class<V> 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!
  }
}

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

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

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

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

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

它与C#非常不同!

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

addToMap(new HashMap<String, Integer>(), new Entry<String,Integer>("FOO", 2), new Entry<String, Integer>("BAR", 8));

    public static<K,V> void addToMap(Map<K,V> map, Entry<K,V>... entries) {
        for (Entry<K,V> entry: entries) {
            map.put(entry.getKey(), entry.getValue());
        }
    }

    public static class Entry<K,V> {

        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<String, Integer> map = new HashMap<String,Integer>() {{
  put("Foo", 1);
  put("Bar", 2);
}};

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

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

如果您更改代码如下

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

它会给你一个编译时错误

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM