未经检查转换为通用 class 实现 Map<String, V>
Unchecked cast to generic class implementing 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
演员表的完整警告:
Type safety: Unchecked cast from Map<capture#3-of ?,Integer>
to StringMap<Integer>
然而,StringMap
class 声明指定了 Map
的第一个类型参数(即 String
),map3
和 StringMap<Integer>
cast 使用与 Map
的第二个类型参数相同的类型(即 Integer
)。据我了解,只要演员表不抛出 ClassCastException
(而且不应该抛出 instanceof
检查),stringMap3
将是有效的 Map<String, Integer>
.
这是 Java 编译器的限制吗?或者是否存在这样一种情况,如果忽略警告,使用某些参数调用 map3 或 stringMap3 的方法可能会导致意外 ClassCastException
?
此演员表不安全。假设您有:
Map<?, Integer> map3 = new HashMap<String,Integer>();
StringMap<Integer> stringMap3 = (StringMap<Integer>)map3;
这将引发异常。您知道自己新建了一个 StringMap<Integer>
并将其分配给 map3 并不重要。您正在做的事情被称为向下转换 Downcasting in Java 了解更多信息。
编辑:您还使所有泛型的问题变得过于复杂,如果没有任何泛型类型,您将遇到完全相同的问题。
实际上答案在 Java 语言规范中。
Section 5.1.10提到如果你使用通配符,那么它将是一个新鲜的捕获类型。
这意味着,Map<String, Integer>
不是 Map<?, Integer>
的子类,因此即使 StringMap<Integer>
可分配给 Map<?, Integer>
类型,因为 Map<?, Integer>
表示一个映射,有一些键类型和 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
行为符合规定。在 Section 5.5.2 of the Java Language Specification 中,未经检查的转换定义为:
A cast from a type S
to a parameterized type T
is unchecked unless at least one of the following is true:
S <: T
All of the type arguments of T
are unbounded wildcards
T <: S
and S
has no subtype X
other than T
where the type arguments of X
are not contained in the type arguments of T
.
(其中 A <: B
表示:“A
是 B
的子类型”)。
在您的第一个示例中,目标类型没有通配符(因此它们都是无界的)。在您的第二个示例中,StringMap<Integer>
实际上是 Map<String, Integer>
的子类型(并且没有第三个条件中提到的子类型 X
)。
然而,在您的第三个示例中,您有一个从 Map<?, Integer>
到 StringMap<Integer>
的转换,并且由于通配符 ?
,两者都不是另一个的子类型。另外,显然,并非所有类型参数都是无限通配符,因此 none 条件适用:这是一个未经检查的异常。
如果代码中出现未经检查的转换,则需要符合规范的 Java 编译器发出警告。
像您一样,我没有看到转换无效的任何情况,因此您可以争辩说这是 Java 编译器的限制,但至少它是一个指定的限制。
当然,不可能提供 "more correct"(更正确?)的答案57=] 语言规范。然而:
用实际术语给出的对观察到的行为的解释比直接向您抛出 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>
并且当您将通配符转换为特定类型时,您总是可以期待这样的警告。 (或有界类型到更严格、更具体的类型。)请注意,在这种情况下,"Integer" 是一条红鲱鱼,你会得到与 Map<?,?> map3 = new HashMap<>();
相同的结果
我想了解为什么这段代码有一个未经检查的转换警告。前两个转换没有警告,但第三个有:
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
演员表的完整警告:
Type safety: Unchecked cast from
Map<capture#3-of ?,Integer>
toStringMap<Integer>
然而,StringMap
class 声明指定了 Map
的第一个类型参数(即 String
),map3
和 StringMap<Integer>
cast 使用与 Map
的第二个类型参数相同的类型(即 Integer
)。据我了解,只要演员表不抛出 ClassCastException
(而且不应该抛出 instanceof
检查),stringMap3
将是有效的 Map<String, Integer>
.
这是 Java 编译器的限制吗?或者是否存在这样一种情况,如果忽略警告,使用某些参数调用 map3 或 stringMap3 的方法可能会导致意外 ClassCastException
?
此演员表不安全。假设您有:
Map<?, Integer> map3 = new HashMap<String,Integer>();
StringMap<Integer> stringMap3 = (StringMap<Integer>)map3;
这将引发异常。您知道自己新建了一个 StringMap<Integer>
并将其分配给 map3 并不重要。您正在做的事情被称为向下转换 Downcasting in Java 了解更多信息。
编辑:您还使所有泛型的问题变得过于复杂,如果没有任何泛型类型,您将遇到完全相同的问题。
实际上答案在 Java 语言规范中。 Section 5.1.10提到如果你使用通配符,那么它将是一个新鲜的捕获类型。
这意味着,Map<String, Integer>
不是 Map<?, Integer>
的子类,因此即使 StringMap<Integer>
可分配给 Map<?, Integer>
类型,因为 Map<?, Integer>
表示一个映射,有一些键类型和 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
行为符合规定。在 Section 5.5.2 of the Java Language Specification 中,未经检查的转换定义为:
A cast from a type
S
to a parameterized typeT
is unchecked unless at least one of the following is true:
S <: T
All of the type arguments of
T
are unbounded wildcards
T <: S
andS
has no subtypeX
other thanT
where the type arguments ofX
are not contained in the type arguments ofT
.
(其中 A <: B
表示:“A
是 B
的子类型”)。
在您的第一个示例中,目标类型没有通配符(因此它们都是无界的)。在您的第二个示例中,StringMap<Integer>
实际上是 Map<String, Integer>
的子类型(并且没有第三个条件中提到的子类型 X
)。
然而,在您的第三个示例中,您有一个从 Map<?, Integer>
到 StringMap<Integer>
的转换,并且由于通配符 ?
,两者都不是另一个的子类型。另外,显然,并非所有类型参数都是无限通配符,因此 none 条件适用:这是一个未经检查的异常。
如果代码中出现未经检查的转换,则需要符合规范的 Java 编译器发出警告。
像您一样,我没有看到转换无效的任何情况,因此您可以争辩说这是 Java 编译器的限制,但至少它是一个指定的限制。
当然,不可能提供 "more correct"(更正确?)的答案57=] 语言规范。然而:
用实际术语给出的对观察到的行为的解释比直接向您抛出 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>
并且当您将通配符转换为特定类型时,您总是可以期待这样的警告。 (或有界类型到更严格、更具体的类型。)请注意,在这种情况下,"Integer" 是一条红鲱鱼,你会得到与 Map<?,?> map3 = new HashMap<>();