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?
If we can't eliminate the “unchecked cast” warning and we're sure that the code provoking the warning is typesafe, we can suppress the warning using the SuppressWarnings(“unchecked”) annotation. When we use the @SuppressWarning(“unchecked”) annotation, we should always put it on the smallest scope possible.
Unchecked cast means that you are (implicitly or explicitly) casting from a generic type to a nonqualified type or the other way around.
An unchecked warning tells a programmer that a cast may cause a program to throw an exception somewhere else. Suppressing the warning with @SuppressWarnings("unchecked") tells the compiler that the programmer believes the code to be safe and won't cause unexpected exceptions.
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.
This cast isn't safe. Let's say you have:
Map<?, Integer> map3 = new HashMap<String,Integer>();
StringMap<Integer> stringMap3 = (StringMap<Integer>)map3;
That's going to throw an exception. It doesn't matter that you know you newed up a StringMap<Integer>
and assigned it to the map3. What you're doing is known as down-casting Downcasting in Java for more info.
EDIT: You're also over complicating the problem with all the generics, you will have the exact same issue without any generic types.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With