简体   繁体   English

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

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

I have come across a strange behavior of Java that seems like a bug. 我遇到了一个奇怪的Java行为,看起来像个bug。 Is it? 是吗? Casting an Object to a generic type (say, K ) does not throw a ClassCastException even if the object is not an instance of K . 即使对象不是K的实例,将Object转换为泛型类型(例如, K )也不会抛出ClassCastException Here is an example: 这是一个例子:

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!!
  }
}

Update : Thanks to cletus and Andrzej Doyle for your helpful answers. 更新 :感谢cletus和Andrzej Doyle提供的有用答案。 Since I can only accept one, I'm accepting Andrzej Doyle's answer because it led me to a solution that I think isn't too bad. 因为我只能接受一个,所以我接受了Andrzej Doyle的回答,因为它让我找到了一个我认为并不太糟糕的解决方案。 I think it's a little better way of initializing a small Map in a one-liner. 我认为这是在单行中初始化小地图的一种更好的方法。

  /**
   * 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;
  }

And then you call it like this: 然后你这样称呼它:

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

Java generics use type erasure, meaning those parameterized types aren't retained at runtime so this is perfectly legal: Java泛型使用类型擦除,这意味着那些参数化类型不会在运行时保留,因此这是完全合法的:

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

because the compiled bytecode looks more like this: 因为编译后的字节码看起来更像是这样的:

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

Java generics are simply syntactic sugar on casting Object s. Java泛型只是构建Object的语法糖。

As cletus says, erasure means that you can't check for this at runtime (and thanks to your casting you can't check this at compile time). 正如cletus所说,擦除意味着你不能在运行时检查这个(并且由于你的转换,你无法在编译时检查它)。

Bear in mind that generics are a compile-time only feature. 请记住,泛型是仅编译时功能。 A collection object does not have any generic parameters, only the references you create to that object. 集合对象没有任何通用参数,只有您为该对象创建的引用 This is why you get a lot of warning about "unchecked cast" if you ever need to downcast a collection from a raw type or even Object - because there's no way for the compiler to verify that the object is of the correct generic type (as the object itself has no generic type). 这就是为什么如果你需要从原始类型甚至Object转发一个集合,你会得到很多关于“unchecked cast”的警告 - 因为编译器无法验证该对象是否具有正确的泛型类型(如对象本身没有泛型类型)。

Also, bear in mind what casting means - it's a way of telling the compiler "I know that you can't necessarily check that the types match, but trust me , I know they do". 另外,请记住铸造意味着什么 - 它是告诉编译器的一种方式“我知道你不一定能检查类型是否匹配,但相信我 ,我知道他们这样做”。 When you override type checking (incorrectly) and then end up with a type mismatch, who ya gonna blame? 当你覆盖类型检查(错误)然后最终导致类型不匹配时,谁会受到责备? ;-) ;-)

It seems like your problem lies around the lack of heterogenous generic data structures. 看来你的问题在于缺乏异构的通用数据结构。 I would suggest that the type signature of your method should be more like private static<K,V> void addToMap(Map<K,V> map, List<Pair<K, V>> vals) , but I'm not convinced that gets you anything really. 我建议您的方法的类型签名应该更像private static<K,V> void addToMap(Map<K,V> map, List<Pair<K, V>> vals) ,但我不相信这真的让你有所收获。 A list of pairs basically is a map, so contructing the typesafe vals parameter in order to call the method would be as much work as just populating the map directly. 对列表基本上是一个映射,因此为了调用方法而构造typesafe vals参数与直接填充映射一样多。

If you really, really want to keep your class roughly as it is but add runtime type-safety, perhaps the following will give you some ideas: 如果你真的,真的想让你的课程大致保持原样但添加运行时类型安全性,或许以下内容会给你一些想法:

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's generics are done using "type erasure", meaning that at runtime, the code doesn't know you have a Map<String, Integer> -- it just sees a Map. Java的泛型是使用“类型擦除”完成的,这意味着在运行时,代码不知道你有一个Map <String,Integer> - 它只是看到一个Map。 And since you're converting the stuff to Objects (by way of your addToMap function's param list), at compile time, the code "looks right". 因为你正在将这些东西转换为Objects(通过你的addToMap函数的参数列表),所以在编译时,代码“看起来正确”。 It doesn't try to run the stuff when compiling it. 它在编译时不会尝试运行这些东西。

If you care about the types at compile time, don't call them Object. 如果您在编译时关心类型,请不要将它们称为Object。 :) Make your addToMap function look like :)使你的addToMap函数看起来像

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

If you want to insert multiple items in the map, you'll want to make a class kinda like java.util's Map.Entry, and wrap your key/value pairs in instances of that class. 如果要在地图中插入多个项目,则需要创建类似java.util的Map.Entry类,并将键/值对包装在该类的实例中。

This is a fairly good explanation of what Generics do and don't do in Java: http://www.angelikalanger.com/GenericsFAQ/FAQSections/ParameterizedTypes.html 这是对Generics在Java中做什么和不做什么的一个相当好的解释: http//www.angelikalanger.com/GenericsFAQ/FAQSections/ParameterizedTypes.html

It is very very different from C#! 它与C#非常不同!

I think you were trying to do something like this? 我想你是想尝试做这样的事情? Where there's compile time safety for the pairs that you're adding to the map: 您要添加到地图的对的编译时安全性:

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;
        }
    }

Edit after comment: 评论后编辑:

Ahh, then perhaps all you're really looking for is this arcane syntax used to harass new hires who just switched to Java. 啊,那么也许所有你真正想要的就是用这种神秘的语法来骚扰那些刚转向Java的新员工。

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

Java generics only apply during compile time and not run time. Java泛型仅适用于编译期间而非运行时。 The issue is the way you have implemented, java compiler does not get a chance to ensure type safety at compile time. 问题是你实现的方式,java编译器没有机会在编译时确保类型安全。

Since ur K,V never say they extend any particular class, during compile time java has no way to know it was supposed to be an integer. 由于你的K,V从不说它们扩展任何特定的类,在编译期间java无法知道它应该是一个整数。

If you change your code as following 如果您更改代码如下

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

it will give you a compile time error 它会给你一个编译时错误

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

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