I'm trying to understand why this code has an unchecked cast warning. The first two casts have no warning, but the third does:
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
}
This is the full warning for the stringMap3
cast:
Type safety: Unchecked cast from
Map<capture#3-of ?,Integer>
toStringMap<Integer>
However, the StringMap
class declaration specifies the first type parameter of Map
(i.e., String
), and both map3
and the StringMap<Integer>
cast use the same type for the second type parameter of Map
(i.e., Integer
). From what I understand, as long as the cast doesn't throw ClassCastException
(and it shouldn't since there is an instanceof
check), stringMap3
would be a valid Map<String, Integer>
.
Is this a limitation of the Java compiler? Or is there a scenario where calling methods of either map3 or stringMap3 with certain arguments may result in an unexpected ClassCastException
if the warning is ignored?
The behavior is as specified. In Section 5.5.2 of the Java Language Specification an unchecked cast is defined as:
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
.
(where A <: B
means: "A
is a subtype of B
").
In your first example, the target type has no wildcards (and thus all of them are unbounded). In your second example, StringMap<Integer>
is actually a subtype of Map<String, Integer>
(and there is no subtype X
as mentioned in the third condition).
In your third example, however, you have a cast from Map<?, Integer>
to StringMap<Integer>
, and, because of the wildcard ?
, neither is a subtype of the other. Also, obviously, not all type parameters are unbounded wildcards, so none of the conditions apply: it is an unchecked exception.
If an unchecked cast occurs in the code, a conforming Java compiler is required to issue a warning.
Like you, I do not see any scenario where the cast would be invalid, so you could argue that it is a limitation of the Java compiler, but at least it is a specified limitation.