Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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?

like image 739
blurredd Avatar asked Nov 23 '15 00:11

blurredd


People also ask

How can I avoid unchecked cast warnings?

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.

What is unchecked cast?

Unchecked cast means that you are (implicitly or explicitly) casting from a generic type to a nonqualified type or the other way around.

What are unchecked warnings in Java?

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.


2 Answers

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.

like image 156
Hoopje Avatar answered Oct 13 '22 01:10

Hoopje


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.

like image 45
Charles Durham Avatar answered Oct 13 '22 01:10

Charles Durham