14

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

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(ie, ) 的第一个类型参数,并且String两者都使用相同的类型作为(ie, )的第二个类型参数。据我了解,只要演员不抛出(并且不应该因为有检查),就会是有效的.map3StringMap<Integer>MapIntegerClassCastExceptioninstanceofstringMap3Map<String, Integer>

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

4

4 回答 4

8

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

除非以下至少一项为真,否则不会检查从类型S到参数化类型的强制转换:T

  • S <: T

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

  • T <: S并且除了的类型参数不包含在 的类型参数中之外S没有子类型。XTXT

(其中的A <: B意思是:“A是”的子类型B)。

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

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

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

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

于 2015-12-02T22:10:48.523 回答
3

实际上答案在 Java 语言规范中。 第 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
于 2015-11-30T19:01:34.033 回答
3

这个演员阵容不安全。假设您有:

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

这将引发异常。您是否知道自己新建了 aStringMap<Integer>并将其分配给了 map3,这并不重要。您正在做的事情在 Java 中被称为向下转换 Downcasting 以获取更多信息。

编辑:您也使所有泛型的问题过于复杂,如果没有任何泛型类型,您将遇到完全相同的问题。

于 2015-11-23T00:37:27.660 回答
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<>();

于 2015-12-04T16:14:55.163 回答