【问题标题】:Casting to generic type in Java doesn't raise ClassCastException?在 Java 中转换为泛型类型不会引发 ClassCastException?
【发布时间】:2010-05-04 16:45:36
【问题描述】:

我遇到了一个奇怪的 Java 行为,它看起来像是一个错误。是吗?即使对象不是K 的实例,将对象转换为泛型类型(例如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's answer,因为它让我找到了一个我认为还不错的解决方案。我认为这是在单行中初始化小地图的更好方法。

  /**
   * 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 generics casting


    【解决方案1】:

    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 泛型只是Objects 的语法糖。

    【讨论】:

    • 那么,我想没有办法让我的功能按预期工作?我在发布后尝试使用vals[i] instanceof KK.class.getName(),但这会产生语法错误。 (我猜是因为在运行时并没有真正的K...)
    【解决方案2】:

    正如 cletus 所说,擦除意味着您无法在运行时检查此内容(并且由于您的强制转换,您无法在编译时检查此内容)。

    请记住,泛型只是编译时的特性。集合object 没有任何通用参数,只有您创建的对该对象的references。这就是为什么如果您需要从原始类型甚至Object 向下转换集合时会收到很多关于“未经检查的强制转换”的警告 - 因为编译器无法验证对象是否属于正确的泛型类型(因为对象本身没有泛型)。

    另外,请记住强制转换的含义 - 这是一种告诉编译器“我知道您不一定可以检查类型是否匹配,但相信我,我知道他们可以” .当您(错误地)覆盖类型检查并最终导致类型不匹配时,您会责备谁? ;-)

    您的问题似乎在于缺乏异构的通用数据结构。我建议你的方法的类型签名应该更像private static&lt;K,V&gt; void addToMap(Map&lt;K,V&gt; map, List&lt;Pair&lt;K, V&gt;&gt; vals),但我不相信这真的能让你得到任何东西。对列表基本上是一个映射,因此构造类型安全的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!
      }
    }
    

    【讨论】:

    • 我只是希望有一种类型安全的方式来在单线中初始化地图,就像在其他一些语言(即 Perl/PHP/Javascript)中你可以执行类似 map = (key1=&gt;val1, key2=&gt;val2, etc); 的操作一样跨度>
    • 是的 - 你不能这样做,因为数组没有编译时类型信息,而泛型参数没有运行时类型信息。传递Class&lt;T&gt; 参数是将编译时和运行时检查联系在一起的唯一方法。
    【解决方案3】:

    Java 的泛型是使用“类型擦除”完成的,这意味着在运行时,代码不知道您有一个 Map——它只看到一个 Map。而且由于您将这些东西转换为对象(通过您的 addToMap 函数的参数列表),因此在编译时,代码“看起来正确”。它不会在编译时尝试运行这些东西。

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

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

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

    【讨论】:

    • 您的 addToMap 示例与 Map.put() 方法基本相同。我希望我能以尽可能紧凑的方式初始化地图。哦,好吧。
    • 你可以,但你会放弃类型安全。不值得,海事组织。这也是我现在更喜欢 .net 而不是 Java 的原因之一。
    【解决方案4】:

    这是对泛型在 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);
    }};
    

    【讨论】:

    • 我基本上是在尝试以紧凑、类型安全的单行方式初始化地图。希望接近 PHP 的 map = (key1=&gt;val1, key2=&gt;val2, etc); 的简单性
    • 在java中有一些晦涩的方法可以做到这一点,请参阅编辑后的答案。请意识到,在许多人看来,这样做通常比“健全的软件工程实践”更“聪明”:)
    • 谢谢,既然你提到了它,我想我以前见过这种语法。
    【解决方案5】:

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

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

    如果你改变你的代码如下

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

    它会给你一个编译时错误

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-07-14
      • 2021-12-01
      • 2016-07-12
      • 2022-11-27
      相关资源
      最近更新 更多