javagenericscastingtype-safetyunchecked-cast

Unchecked cast to generic class implementing Map<String, V>


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> to StringMap<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?


Solution

  • 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 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.

    (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.