我试图了解为什么此代码有未经检查的强制警告。前两个演员都没有警告,但第三个演员没有警告:
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
),以及map3
和StringMap<Integer>
强制使用Map
的第二个类型参数的相同类型(即Integer
)。根据我的理解,只要演员不会抛出ClassCastException
(并且因为instanceof
检查不应该),stringMap3
将是有效的Map<String, Integer>
。
这是Java编译器的限制吗?或者是否有一种情况,如果忽略警告,使用某些参数调用map3或stringMap3的方法可能会导致意外ClassCastException
?
答案 0 :(得分:8)
行为符合指定。在Section 5.5.2 of the Java Language Specification中,未经检查的强制转换定义为:
取消选中从
S
类型到参数化类型T
的强制转换,除非至少满足下列条件之一:
S <: T
T
的所有类型参数都是无界通配符
T <: S
和S
除了X
之外没有T
,X
的类型参数不包含在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<>();
相同的东西