取消选中强制转换为实现Map <string,v =“”>

时间:2015-11-23 00:30:04

标签: java generics casting type-safety unchecked-cast

我试图了解为什么此代码有未经检查的强制警告。前两个演员都没有警告,但第三个演员没有警告:

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<capture#3-of ?,Integer>StringMap<Integer>

取消选中

但是,StringMap类声明指定Map的第一个类型参数(即String),以及map3StringMap<Integer>强制使用Map的第二个类型参数的相同类型(即Integer)。根据我的理解,只要演员不会抛出ClassCastException(并且因为instanceof检查不应该),stringMap3将是有效的Map<String, Integer>

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

4 个答案:

答案 0 :(得分:8)

行为符合指定。在Section 5.5.2 of the Java Language Specification中,未经检查的强制转换定义为:

  

取消选中从S类型到参数化类型T的强制转换,除非至少满足下列条件之一:

     
      
  • S <: T

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

  •   
  • T <: SS除了X之外没有TX的类型参数不包含在T的类型参数中A <: B

  •   

(其中A表示:“B是[{1}}”的子类型。)

在第一个示例中,目标类型没有通配符(因此它们都是无界的)。在第二个示例中,StringMap<Integer>实际上是Map<String, Integer>的子类型(并且在第三个条件中没有提到子类型X)。

但是,在第三个示例中,您有一个从Map<?, Integer>StringMap<Integer>的强制转换,并且由于通配符?,它们都不是另一个的子类型。另外,显然,并非所有类型参数都是无界通配符,因此没有条件适用:它是未经检查的异常。

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

和你一样,我没有看到演员阵容无效的任何情况,所以你可以说它是Java编译器的限制,但至少它是一个特定的限制。

答案 1 :(得分:3)

这个演员阵容不安全。让我们说你有:

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

那会引发异常。您知道自己已经新建StringMap<Integer>并将其分配给地图3并不重要。您正在做的事情被称为向下投射Downcasting in Java以获取更多信息。

编辑:您还要将所有泛型问题复杂化,您将遇到完全相同的问题而没有任何泛型类型。

答案 2 :(得分:3)

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

这意味着,Map<String, Integer>不是Map<?, Integer>的子类,因此即使StringMap<Integer>可分配给Map<?, Integer>类型,因为Map<?, Integer>表示具有某些键类型和整数值的映射,对于StringMap<Integer>是真的,强制转换是不安全的。所以这就是为什么你得到一个未经检查的演员警告。

从编译的角度来看,赋值和转换之间可能有任何东西,所以即使map3是转换操作中的StringMap<Integer>的实例,map3也有Map<?, Integer>作为其类型,因此警告是完全合法的。

并回答你的问题:是的,直到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

答案 3 :(得分:3)

当然,与根据Java语言规范解释观察到的行为的答案相比,不可能提供“更正确”(更正确的?)的答案。但是:

  • 实际上给出的观察到的行为的解释比刚刚抛出JLS的解释更容易理解和更容易记住。因此,实际解释通常更多可用而不是JLS方面的解释。

  • JLS正是它的方式,而不是任何其他方式,因为它需要满足实际约束。鉴于语言的基本选择,JLS的细节往往不是其他方式。这意味着特定行为的实际原因可以被认为是比JLS 更重要,因为它们塑造了JLS,JLS没有塑造它们。

因此,对实际情况进行了实际解释。

以下内容:

Map<?, ?> map1 = ...;
StringToIntegerMap stringMap1 = (StringToIntegerMap)map1; 

未提供未经检查的强制转换警告,因为您未将转换为泛型类型。它与执行以下操作相同:

Map map4 = ...; //gives warning "raw use of generic type"; bear with me.
StringToIntegerMap stringMap4 = (StringToIntegerMap)map4; //no unchecked warning!

以下内容:

Map<String, Integer> map2 = ...;
StringMap<Integer> stringMap2 = (StringMap<Integer>)map2;

没有给出未经检查的强制转换警告,因为左侧的泛型参数与右侧的泛型参数匹配。 (两者都是<String,Integer>

以下内容:

Map<?, Integer> map3 = ...;
StringMap<Integer> stringMap3 = (StringMap<Integer>)map3;

确实提供了未经检查的强制转换警告,因为左侧是<String,Integer>但右侧是<?,Integer>,当您将通配符转换为特定类型时,您总是可以期待这样的警告。 (或者是有限类型到更严格限制,更具体的类型。)请注意,在这种情况下,“整数”是一个红色鲱鱼,你会得到与Map<?,?> map3 = new HashMap<>();相同的东西