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

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?

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.

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.

Actually the answer is in the Java Language Specification.
Section 5.1.10 mentions that if you use a wildcard, then it will be a fresh capture type.
This implies that, Map<String, Integer> is not a subclass of Map<?, Integer>, and therefore even if the StringMap<Integer> is assignable to a Map<?, Integer> type, because Map<?, Integer> represents a map with some key type and Integer values, which is true to StringMap<Integer>, the cast is unsafe. So this is why you get an unchecked cast warning.
From the compiler point of view between the assignment, and the cast there could be anything, so even if map3 is an instance of StringMap<Integer> at the cast operation, map3 has Map<?, Integer> as its type, so the warning is completely legit.
And to answer for your question: yes, until the map3 instance has only strings as keys, and your code could not possibly guarantee that.
See why not:
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));
}
}
The result:
null:2
foo:0
Exception in thread "main" java.lang.ClassCastException: java.lang.Double cannot be cast to java.lang.String

Of course, it is not possible to provide an answer which is "more correct" (correctier?) than one which explains the observed behaviour in terms of the Java Language Specification. However:
An explanation of the observed behaviour given in practical terms can be easier to follow and easier to remember than an explanation which just throws the JLS at you. As a result, a practical explanation is often more usable than an explanation in terms of the JLS.
The JLS is precisely the way it is and not in any other way, because it needs to satisfy practical constraints. Given the fundamental choices made by the language, quite often the details of the JLS could not have been any other way than the way it is. This means that the practical reasons for a specific behaviour can be thought of as more important than the JLS, because they shaped the JLS, the JLS did not shape them.
So, a practical explanation of what is happening follows.
The following:
Map<?, ?> map1 = ...;
StringToIntegerMap stringMap1 = (StringToIntegerMap)map1;
gives no unchecked cast warning because you are not casting to a generic type. It is the same as doing the following:
Map map4 = ...; //gives warning "raw use of generic type"; bear with me.
StringToIntegerMap stringMap4 = (StringToIntegerMap)map4; //no unchecked warning!
The following:
Map<String, Integer> map2 = ...;
StringMap<Integer> stringMap2 = (StringMap<Integer>)map2;
gives no unchecked cast warning because generic arguments of left hand side match generic arguments of right hand side. (Both are <String,Integer>)
The following:
Map<?, Integer> map3 = ...;
StringMap<Integer> stringMap3 = (StringMap<Integer>)map3;
does give an unchecked cast warning because left hand side is <String,Integer> but right hand side is <?,Integer> and you can always expect such a warning when you cast a wildcard to specific type. (Or a bounded type to a more strictly bounded, more specific type.) Note that "Integer" in this case is a red herring, you would get the same thing with Map<?,?> map3 = new HashMap<>();

Related

Java generics How to accept any derived type in generic parameter

In The below 2 lines of code
HashMap<Integer, ?extends Collection<String>> map=
new HashMap<Integer, TreeSet<String>>();
map.put(1,new TreeSet<String>());
Line 2 :The method put(Integer, capture#1-of ? extends Collection) in the type HashMap> is not applicable for the arguments (int, TreeSet)
Line 1: this has no error.
Why is the same generic type (TreeSet< String >) allowed in Line 1 but not allowed in Line 2?
Edit :
With super instead of extends ,why is the following is NOT allowed.
HashMap<Integer, ?super Collection<String>> map=new HashMap(<Integer, TreeSet<String>>());
but
HashMap<Integer, ?super Collection<String>> map=new HashMap();
map.put(1,new TreeSet<String>());
is allowed
The reason you get a compiler error is the same reason you cannot add a Dog to a List<? extends Animal> -- you cannot call a method with a generic parameter when the type of the reference variable has an upper bound wildcard. The map variable's value type parameter could refer to any type that matches ? extends Collection<String>, perhaps as HashMap<Integer, LinkedList<String>>. You could legally insert this line before the call to put:
map = new HashMap<Integer, LinkedList<String>>();
The compiler doesn't know the exact type that's really in the map, so it must, at compile time, prevent you from putting a TreeSet<String> in as a value to a map whose value could be something like LinkedList<String>.
To put a value in map (besides null), you must remove the wildcard.
HashMap<Integer, TreeSet<String>> map =
new HashMap<Integer, TreeSet<String>>();
As JB Nizet has commented, you still can put a value of any Collection such as TreeSet if you remove the wildcard but keep Collection.
HashMap<Integer, Collection<String>> map =
new HashMap<Integer, Collection<String>>();
(Also, the diamond operator can simplify the declarations here.)
In response to the changes added to the question:
Here, you've used a lower bound.
HashMap<Integer, ? super Collection<String>> map = new HashMap<Integer, TreeSet<String>>());
The type parameter may be Collection<String> or any supertype, such as Object. This disallows subtypes such as TreeSet<String>. Java's generics are invariant. The only reason any variation from Collection<String> is allowed is because of the wildcard.
HashMap<Integer, ? super Collection<String>> map = new HashMap<>();
map.put(1, new TreeSet<String>());
This is allowed because any supertype of Collection<String> will match any subtype as an argument. After all, a TreeSet<String> is an Object. A TreeSet<String> can be put as a value to map, whether it's a HashMap<Integer, Object> or a HashMap<Integer, Collection<String>>, or any type in between. The compiler can prove type safety, so it allows the call.
The declaration of the map tells the compiler that a value inside in the map is some collection of strings. At runtime, it could be a TreeSet but it could also be some other collection type. Hence the compiler cannot allow putting a TreeSet since it may actually contain ArrayList values.
More generally, whenever you use a bound type argument using the ? wildcard, you are practically only allowed to read from the map (e.g. iterate over its elements). In other words, you can always do:
for(Iterator<? extends Collection<String>> iterator = map.values().iterator(); iterator.hasNext();) {
Collection<String> collection = iterator.next();
...
}
In your case, however, since you're adding a TreeSet it means that most likely you know that the map will contain TreeSet values, so you don't need to use a wildcard:
HashMap<Integer, TreeSet<String>> map = new HashMap<>(); // In Java 7+, you can use the diamond operator when creating the HashMap
Compiler says:
no suitable method found for put(int,TreeSet)
map.put(1, new TreeSet());
method HashMap.put(Integer,CAP#1) is not applicable
(actual argument TreeSet cannot be converted to CAP#1 by method invocation conversion)
method AbstractMap.put(Integer,CAP#1) is not applicable
(actual argument TreeSet cannot be converted to CAP#1 by method invocation conversion) where CAP#1 is a fresh type-variable:
CAP#1 extends Collection from capture of ? extends Collection
I think it's because of type ambiguity for compiler and as AR.3 sayed:
The declaration of the map tells the compiler that a value inside in
the map is some collection of strings. At runtime, it could be a
TreeSet but it could also be some other collection type.
If the type be certain for the compiler the code will compiles and runs without any problem, for instance you can write your code as below which compiles and runs successfully:
public <T extends Collection<String>> void someVoid(){
//...
//...
HashMap<Integer, T > map
= new HashMap<>();
map.put(1,(T) (new TreeSet<String>()));
//...
//...
}
After reading the other answers and thinking some more about the original question (which was really about having the "? extends" part versus not having it), I came up with the following example, which should be easier to understand.
Suppose we have this method:
void doSomething(Collection<? extends Number> numbers) { ... }
And the following usages elsewhere:
Collection<Integer> integers = asList(1, 2, 3);
Collection<Long> longs = asList(1L, 2L, 3L);
doSomething(integers);
doSomething(longs);
All of the above compiles fine. Now, let's ask, what can the doSomething method do with the collection it receives? Can it add some number to it? The answer is no, it can't. And that's because it would be unsafe; for example, if it added an Integer (which is a Number), then the doSomething(longs) call would fail. So, to prevent such possibilities the compiler disallows any attempt to add any Number at all to a Collection<? extends Number>.
Similarly, what if the method was declared as:
void doSomething(Collection<Number> numbers) { ... }
In this case, the method can add any number to the collection, but then calls like doSomething(integers) become compiler errors, since a collection of Integer can only accept integers. So, again, the compiler prevents the possiblity for ClassCastExceptions later, ensuring the code is type safe.

Why there is no warning while casting from object to unbounded wildcard collection?

Why is there no warning for the below code?
public void some(Object a){
Map<?, ?> map = **(Map<?,?>)a**; //converting unknown object to map
}
I expected the RHS to have an unchecked warning.
While this code has a warning:
public void some(Object a){
Map<Object, Object> map = **(Map<Object,Object>)a**;
//converting unknown object to Map<Object,Object>
}
Also, for below case there is no warning:
String str = (String) request.getAttribute("asd") //returns Object
Does this mean that unchecked warnings came with generics? There were no such warnings before introduction of generics in Java?
Yes, the unchecked warning is only relevant to generic types.
What it means is: this cast from Object to Map<T1, T2> might succeed because the object is indeed a Map, but the runtime has no way, due to type erasure, to check that it's a Map<T1, T2>. It might very well be a Map<T3, T4>. So you might very well break the type-safety of the map by putting T1, T2 elements inside, or get a ClassCastException when trying to read values from the map.
You have no warning for the first cast because you're casting to a Map<?, ?>, which means that the key and the value type is unknown, which is true. You won't be able to perform a type-unsafe operation on such a map without additional casts: you can't add anything to such a map, and the only thing you can get out of it is instances of Object.
You get no "unchecked" warning because the cast is completely "checked" -- a cast to Map<?,?> only needs to ensure that the object is a Map (and nothing else), and that is completely checkable at runtime. In other words, Map<?,?> is a reifiable type.

Generics in HashMap implementation

In the Java implementation, I found
transient Entry[] table;
which is initiated in constructor as
table = new Entry[capacity];
I know and understand that creating generic array is not allowed but then what I fail to understand is that how the whole thing works. I mean when we do something like
HashMap<Integer, String> hMap = new HashMap<Integer, String>();
How does above codes leads to creating an Entry array of type <Integer, String>
Well, few people are not able to understand what I am asking. To rephrase what I am asking is what is the point in doing something like
HashMap<Integer, String> hMap = new HashMap<Integer, String>();
When it does not result in
Entry<Integer, String>
Generics are a compile-time safety. At runtime, the map only know about Objects. This is known as type erasure. To scare you even more, the following code will run without problem:
Map<Integer, Integer> safeMap = new HashMap<>();
Map unsafeMap = safeMap;
unsafeMap.put("hello", "world");
You'll get a warning at compile time, because you're using a raw Map instead of a generic one, but at runtime, no check is done at all, because the map is a good old map able of storing any object. Only the compiler prevents you from adding Strings in a map or integers.
The implementation makes an array of Entry<K,V> objects of type
static class Entry<K,V> implements Map.Entry<K,V>
without providing generic type parameters (source). This is allowed, but it comes with understanding that the compiler is no longer guarantees type safety. For example, in other places in code you could write
Entry<K,V> e = table[bucketIndex];
and the compiler will let you do that. If you know for sure that you always set elements of table[] to null or Entry<K,V>, then you know that the assignment is correct.
The reason this works without a problem is that generic types in Java are implemented through type erasure, i.e. there is no difference at runtime between Entry<K,V> objects Entry<Integer,Integer> and Entry<String,Long>.
Try to think of Java Generics this way: type parameters only apply to the static type of reference-typed expressions and do not apply to the type of actual instances being referred to by the reference values at runtime.
I find the above key to developing the proper intuitions when reading Java code. So the next time you see
new HashMap<Integer, String>()
read it as follows: "This is an instance creation expression of the type HashMap<Integer, String>. At runtime this expression will yield a reference to an instance of the HashMap class." As long as the compiler can precisely track what you do with the result of that expression, it can maintain the knowledge that this is indeed a HashMap<Integer, String>, but no further than that.
Now, since the static type system is not powerful enough to track the type parameters on the component type of arrays (the fact that Java's array types are covariant plays strongly here), the code is forced to break out of the static type safety network. The key observation is that on its own, this does not make the code incorrect, it only constrains the power of the compiler to find programming mistakes. This is why Java allows you to make unchecked casts from raw into generic types, although not without a warning which marks the spot where you have left the provinces of static type safety.

Understanding Generics in Object Initialization

I have a basic question about generics in Java: what is difference between the following two initializations of a map?
Map<String, String> maplet1 = new HashMap<String, String>();
Map<String, String> maplet2 = new HashMap();
I understand the the first initialization is specifying the generics in the object construction, but I don't understand the underlying ramifications of doing this, rather than the latter object construction (maplet2). In practice, I've always seen code use the maplet1 construction, but I don't understand where it would be beneficial to do that over the other.
The second Map is assigned to a raw type and will cause a compiler warning. You can simply use the first version to eliminate the warning.
For more see: What is a raw type and why shouldn't we use it?
The first one is type-safe.
You can shorthand the right side by using the diamond operator <>. This operator infers the type parameters from the left side of the assignment.
Map<String, String> maplet2 = new HashMap<>();
Lets understand the concept of Erasure. At RUNTIME HashMap<String, String>() and HashMap() are the same represented by HashMap.
The process of converting HashMap<String,String> to HashMap (Raw Type) is called Erasure.
Without the use of Generics , you have to cast , say the value in the Map , to String Explicitly every time.
The use of Generics forces you to Eliminate cast.
If you don't use Generics , there will be high probability that a Future Developer might insert another type of Object which will cause ClassCastException

TreeMap generic argument warning

Map<Date, Integer> m = new HashMap<Date, Integer>(); // line 1
Map<Date, Integer> sMap = new TreeMap(m); // line 2
Line 2 gives this error:
Type safety: The expression of type TreeMap needs unchecked conversion
to conform to Map
The solution I found is this: How do I fix "The expression of type List needs unchecked conversion...'?
But, is it safe to simply #SuppressWarnings("unchecked") or is there a scenario when my code will failed. More generally, when can we safely add the #SuppressWarnings("unchecked")?
Try specifying the generic types of the TreeMap when you instantiate it.
Map<Date, Integer> m = new HashMap<Date, Integer>(); // line 1
Map<Date, Integer> sMap = new TreeMap<Date,Integer>(m);
This answer assumes you are using java.util.TreeMap. See: http://docs.oracle.com/javase/7/docs/api/
If you use Java 7, you can use the diamond syntax:
Map<Date, Integer> m = new HashMap<>();
Map<Date, Integer> sMap = new TreeMap<>(m);
The correct way is:
1. Map<Date, Integer> m = new HashMap<Date, Integer>();
2. Map<Date, Integer> sMap = new TreeMap<Date, Integer>(m);
You can also supress the "unchecked" warnings if you are sure about the generic type. In this case, you are.
As already said, adding generic parameters to the TreeMap resolves the problem, as the compiler can now guarantee that no invalid casts will happen. If you omit the generic parameters the compiler cannot give you this guarantee and therefore warns you. Then it is your responsibility that only objects of the correct type are stored in the TreeMap.
If you are you sure that only the correct objects are put into the TreeMap you can safely ignore the warning. If it fails and you encounter class cast exceptions on runtime, it is your own fault (:
Generally speaking if you supress a warning you are effectively ignoring a hint of the compiler that there may be a problem in your code. But the compiler is dumb and there are situations where you can safely say that no problems will occur (e.g. you have other checks in place or a design that does not permit the errors to happen). If that is the case you can suppress the warning.

Categories

Resources