提问者:小点点

未选中强制转换为实现Map<String, V>的泛型类


我试图理解为什么这段代码有一个未经检查的强制转换警告。前两个强制转换没有警告,但第三个有:

class StringMap<V> extends HashMap<String, V> {
}

class StringToIntegerMap extends HashMap<String, Integer> {
}

Map<?, ?> map1 = new StringToIntegerMap();
if (map1 instanceof StringToIntegerMap) {
    StringToIntegerMap stringMap1 = (StringToIntegerMap)map1; //no unchecked cast warning
}

Map<String, Integer> map2 = new StringMap<>();
if (map2 instanceof StringMap) {
    StringMap<Integer> stringMap2 = (StringMap<Integer>)map2; //no unchecked cast warning
}

Map<?, Integer> map3 = new StringMap<>();
if (map3 instanceof StringMap) {
    StringMap<Integer> stringMap3 = (StringMap<Integer>)map3; //unchecked cast warning
}

这是stringMap3强制转换的完整警告:

类型安全:Map中未经检查的强制转换

但是,StringMap类声明指定了Map的第一个类型参数(即String),以及map3StringMap

这是Java编译器的限制吗?或者如果忽略警告,使用某些参数调用map3或stringMap3的方法可能会导致意外的ClassCastException


共3个答案

匿名用户

行为符合规定。在Java语言规范的第5.5.2节中,未经检查的强制转换定义为:

从类型S到参数化类型T的强制转换未选中,除非以下至少一项为真:

>

  • S

    T的所有类型参数都是无界通配符

    T

    (其中A

    在您的第一个示例中,目标类型没有通配符(因此它们都是无界的)。在您的第二个示例中,StringMap

    但是,在您的第三个示例中,您有来自Map的强制转换

    如果代码中发生未经检查的强制转换,则需要符合要求的Java编译器发出警告。

    像你一样,我没有看到强制转换无效的任何场景,所以你可以争辩说这是Java编译器的限制,但至少它是一个指定的限制。

  • 匿名用户

    这个石膏不安全。假设你有:

    Map<?, Integer> map3 = new HashMap<String,Integer>();
    StringMap<Integer> stringMap3 = (StringMap<Integer>)map3;
    

    这将抛出一个异常。你知道你更新了一个StringMap并不重要

    编辑:你也把所有泛型的问题复杂化了,没有任何泛型类型,你会遇到完全相同的问题。

    匿名用户

    实际上答案在Java语言规范中。第5.1.10节提到,如果您使用通配符,那么它将是一个新的捕获类型。

    这意味着,Map

    从编译器的角度来看,赋值和强制转换之间可能有任何东西,所以即使map3是StringMap的实例

    回答您的问题:是的,直到map3实例只有字符串作为键,而您的代码不可能保证这一点。

    看看为什么不:

    Map<?, Integer> map3 = new StringMap<Integer>();
    
    // valid operation to use null as a key. Possible NPE depending on the map implementation
    // you choose as superclass of the StringMap<V>.
    // if you do not cast map3, you can use only null as the key.
    map3.put(null, 2); 
    
    // but after an other unchecked cast if I do not want to create an other storage, I can use
    // any key type in the same map like it would be a raw type.
    ((Map<Double, Integer>) map3).put(Double.valueOf(0.3), 2); 
    
    // map3 is still an instance of StringMap
    if (map3 instanceof StringMap) {
        StringMap<Integer> stringMap3 = (StringMap<Integer>) map3; // unchecked cast warning
        stringMap3.put("foo", 0);
        for (String s : stringMap3.keySet()){
            System.out.println(s+":"+map3.get(s));
        }
    }
    

    结果:

    null:2
    foo:0
    Exception in thread "main" java.lang.ClassCastException: java.lang.Double cannot be cast to java.lang.String