By using a raw type, you can't have the compiler prevent you from adding any old key and value into the map. That might be fine if your keys and values happen to be Object
s, but that's not a very common thing in my experience.
By using explicit type bounds, your code won't compile if you try to pass in parameters of the wrong type. For instance:
Map<String, String> genericMap = new HashMap<>();
genericMap.put(new Object(), new Object()); // compiler error.
However, if you were to write:
Map rawMap = genericMap;
rawMap.put(new Object(), new Object()); // fine.
So now basically all bets are off as to what is contained in that map. If you pass genericMap
to a distant method which expected a Map<String, String>
and is going to consume values from it, you are going to get a runtime failure, probably a ClassCastException
:
import java.util.HashMap;
import java.util.Map;
class DontDoThis {
@SuppressWarnings("unchecked")
public static void main(String[] args) {
Map<String, String> genericMap = new HashMap<>();
Map rawMap = genericMap;
rawMap.put(new Object(), new Object());
useMap(genericMap);
}
private static void useMap(Map<String, String> map) {
for (String key : map.keySet()) {} // ClassCastException.
}
}
If you use wildcard bounds, you can't do things like put new values into the map:
Map<?, ?> wildcardMap = genericMap;
genericMap.put("key1", "value1"); // fine.
wildcardMap.put("key", "value"); // compiler error.
You can, however, still use the values from wildcardMap
; all you can know about them is that the values are subtypes of Object
:
for (Map.Entry<?, ?> entry : wildcardMap.entrySet()) {
Object key = entry.getKey();
Object value = entry.getValue();
// Use key and value somehow.
}
As to the question of how it can be caught in a test - you shouldn't write tests for this kind of stuff - successful compilation is your test!
The kind of coverage that compile-time type safety will give you is substantially greater, with substantially less code, than any test you could practically hope to write.