JAVA: List<List<? extends Object>> vs List<List<Object>> [duplicate] - java

I'm using Eclipse to help me clean up some code to use Java generics properly. Most of the time it's doing an excellent job of inferring types, but there are some cases where the inferred type has to be as generic as possible: Object. But Eclipse seems to be giving me an option to choose between a type of Object and a type of '?'.
So what's the difference between:
HashMap<String, ?> hash1;
and
HashMap<String, Object> hash2;

An instance of HashMap<String, String> matches Map<String, ?> but not Map<String, Object>. Say you want to write a method that accepts maps from Strings to anything: If you would write
public void foobar(Map<String, Object> ms) {
...
}
you can't supply a HashMap<String, String>. If you write
public void foobar(Map<String, ?> ms) {
...
}
it works!
A thing sometimes misunderstood in Java's generics is that List<String> is not a subtype of List<Object>. (But String[] is in fact a subtype of Object[], that's one of the reasons why generics and arrays don't mix well. (arrays in Java are covariant, generics are not, they are invariant)).
Sample:
If you'd like to write a method that accepts Lists of InputStreams and subtypes of InputStream, you'd write
public void foobar(List<? extends InputStream> ms) {
...
}
By the way: Joshua Bloch's Effective Java is an excellent resource when you'd like to understand the not so simple things in Java. (Your question above is also covered very well in the book.)

Another way to think about this problem is that
HashMap<String, ?> hash1;
is equivalent to
HashMap<String, ? extends Object> hash1;
Couple this knowledge with the "Get and Put Principle" in section (2.4) from Java Generics and Collections:
The Get and Put Principle: use an
extends wildcard when you only get
values out of a structure, use super
wildcard when you only put values into
a structure, and don't use a wildcard
when you both get and put.
and the wild card may start making more sense, hopefully.

It's easy to understand if you remember that Collection<Object> is just a generic collection that contains objects of type Object, but Collection<?> is a super type of all types of collections.

The answers above covariance cover most cases but miss one thing:
"?" is inclusive of "Object" in the class hierarchy. You could say that String is a type of Object and Object is a type of ?. Not everything matches Object, but everything matches ?.
int test1(List<?> l) {
return l.size();
}
int test2(List<Object> l) {
return l.size();
}
List<?> l1 = Lists.newArrayList();
List<Object> l2 = Lists.newArrayList();
test1(l1); // compiles because any list will work
test1(l2); // compiles because any list will work
test2(l1); // fails because a ? might not be an Object
test2(l2); // compiled because Object matches Object

You can't safely put anything into Map<String, ?>, because you don't know what type the values are supposed to be.
You can put any object into a Map<String, Object>, because the value is known to be an Object.

Declaring hash1 as a HashMap<String, ?> dictates that the variable hash1 can hold any HashMap that has a key of String and any type of value.
HashMap<String, ?> map;
map = new HashMap<String, Integer>();
map = new HashMap<String, Object>();
map = new HashMap<String, String>();
All of the above is valid, because the variable map can store any of those hash maps. That variable doesn't care what the Value type is, of the hashmap it holds.
Having a wildcard does not, however, let you put any type of object into your map. as a matter of fact, with the hash map above, you can't put anything into it using the map variable:
map.put("A", new Integer(0));
map.put("B", new Object());
map.put("C", "Some String");
All of the above method calls will result in a compile-time error because Java doesn't know what the Value type of the HashMap inside map is.
You can still get a value out of the hash map. Although you "don't know the value's type," (because you don't know what type of hash map is inside your variable), you can say that everything is a subclass of Object and, so, whatever you get out of the map will be of the type Object:
HashMap<String, Integer> myMap = new HashMap<>();// This variable is used to put things into the map.
myMap.put("ABC", 10);
HashMap<String, ?> map = myMap;
Object output = map.get("ABC");// Valid code; Object is the superclass of everything, (including whatever is stored our hash map).
System.out.println(output);
The above block of code will print 10 to the console.
So, to finish off, use a HashMap with wildcards when you do not care (i.e., it does not matter) what the types of the HashMap are, for example:
public static void printHashMapSize(Map<?, ?> anyMap) {
// This code doesn't care what type of HashMap is inside anyMap.
System.out.println(anyMap.size());
}
Otherwise, specify the types that you need:
public void printAThroughZ(Map<Character, ?> anyCharacterMap) {
for (int i = 'A'; i <= 'Z'; i++)
System.out.println(anyCharacterMap.get((char) i));
}
In the above method, we'd need to know that the Map's key is a Character, otherwise, we wouldn't know what type to use to get values from it. All objects have a toString() method, however, so the map can have any type of object for its values. We can still print the values.

Related

Unbounded wildcards vs Object type parameter in Java [duplicate]

I'm using Eclipse to help me clean up some code to use Java generics properly. Most of the time it's doing an excellent job of inferring types, but there are some cases where the inferred type has to be as generic as possible: Object. But Eclipse seems to be giving me an option to choose between a type of Object and a type of '?'.
So what's the difference between:
HashMap<String, ?> hash1;
and
HashMap<String, Object> hash2;
An instance of HashMap<String, String> matches Map<String, ?> but not Map<String, Object>. Say you want to write a method that accepts maps from Strings to anything: If you would write
public void foobar(Map<String, Object> ms) {
...
}
you can't supply a HashMap<String, String>. If you write
public void foobar(Map<String, ?> ms) {
...
}
it works!
A thing sometimes misunderstood in Java's generics is that List<String> is not a subtype of List<Object>. (But String[] is in fact a subtype of Object[], that's one of the reasons why generics and arrays don't mix well. (arrays in Java are covariant, generics are not, they are invariant)).
Sample:
If you'd like to write a method that accepts Lists of InputStreams and subtypes of InputStream, you'd write
public void foobar(List<? extends InputStream> ms) {
...
}
By the way: Joshua Bloch's Effective Java is an excellent resource when you'd like to understand the not so simple things in Java. (Your question above is also covered very well in the book.)
Another way to think about this problem is that
HashMap<String, ?> hash1;
is equivalent to
HashMap<String, ? extends Object> hash1;
Couple this knowledge with the "Get and Put Principle" in section (2.4) from Java Generics and Collections:
The Get and Put Principle: use an
extends wildcard when you only get
values out of a structure, use super
wildcard when you only put values into
a structure, and don't use a wildcard
when you both get and put.
and the wild card may start making more sense, hopefully.
It's easy to understand if you remember that Collection<Object> is just a generic collection that contains objects of type Object, but Collection<?> is a super type of all types of collections.
The answers above covariance cover most cases but miss one thing:
"?" is inclusive of "Object" in the class hierarchy. You could say that String is a type of Object and Object is a type of ?. Not everything matches Object, but everything matches ?.
int test1(List<?> l) {
return l.size();
}
int test2(List<Object> l) {
return l.size();
}
List<?> l1 = Lists.newArrayList();
List<Object> l2 = Lists.newArrayList();
test1(l1); // compiles because any list will work
test1(l2); // compiles because any list will work
test2(l1); // fails because a ? might not be an Object
test2(l2); // compiled because Object matches Object
You can't safely put anything into Map<String, ?>, because you don't know what type the values are supposed to be.
You can put any object into a Map<String, Object>, because the value is known to be an Object.
Declaring hash1 as a HashMap<String, ?> dictates that the variable hash1 can hold any HashMap that has a key of String and any type of value.
HashMap<String, ?> map;
map = new HashMap<String, Integer>();
map = new HashMap<String, Object>();
map = new HashMap<String, String>();
All of the above is valid, because the variable map can store any of those hash maps. That variable doesn't care what the Value type is, of the hashmap it holds.
Having a wildcard does not, however, let you put any type of object into your map. as a matter of fact, with the hash map above, you can't put anything into it using the map variable:
map.put("A", new Integer(0));
map.put("B", new Object());
map.put("C", "Some String");
All of the above method calls will result in a compile-time error because Java doesn't know what the Value type of the HashMap inside map is.
You can still get a value out of the hash map. Although you "don't know the value's type," (because you don't know what type of hash map is inside your variable), you can say that everything is a subclass of Object and, so, whatever you get out of the map will be of the type Object:
HashMap<String, Integer> myMap = new HashMap<>();// This variable is used to put things into the map.
myMap.put("ABC", 10);
HashMap<String, ?> map = myMap;
Object output = map.get("ABC");// Valid code; Object is the superclass of everything, (including whatever is stored our hash map).
System.out.println(output);
The above block of code will print 10 to the console.
So, to finish off, use a HashMap with wildcards when you do not care (i.e., it does not matter) what the types of the HashMap are, for example:
public static void printHashMapSize(Map<?, ?> anyMap) {
// This code doesn't care what type of HashMap is inside anyMap.
System.out.println(anyMap.size());
}
Otherwise, specify the types that you need:
public void printAThroughZ(Map<Character, ?> anyCharacterMap) {
for (int i = 'A'; i <= 'Z'; i++)
System.out.println(anyCharacterMap.get((char) i));
}
In the above method, we'd need to know that the Map's key is a Character, otherwise, we wouldn't know what type to use to get values from it. All objects have a toString() method, however, so the map can have any type of object for its values. We can still print the values.

Two ways of java generic method declaration

While there are similar questions around like this and this; those don't answer what are the difference between two ways of generic declaration and why one of it compiles but other not in the following example?
The one that doesn't compile was based on official tutorial as for me.
public void processMap1(Map<String, List<?>> map) {
}
public <T> void processMap2(Map<String, List<T>> map) {
}
public void f() {
Map<String, List<String>> map = new HashMap<>();
processMap1(map); // ERROR
processMap2(map); // OK
}
I want my methods to work with both Map<String, List<String>> and Map<String, List<Object>>
You have a method that expects an object of type X. In order to pass an object to this method, this object must have a type Y that is a subtype of X.
This may be obvious for simple cases:
void processNumber(Number number) { ... }
void callIt()
{
Integer integer = null;
processNumber(integer); // Fine. Integer is a subtype of Number
String string = null;
processNumber(string); // Error. String is NOT a subtype of Number
}
So far, so good. But now you're entering the confusing world of subtype-relationships for parameterized types. Angelika Langer goes into the details in her Generics FAQ: Which super-subtype relationships exist among instantiations of generic types?:
This is fairly complicated and the type relationships are best determined setting up tables as explained in this FAQ entry.
So I won't even try to reproduce this here, but only try to explain (simplified) why the second version works, but the first one doesn't.
The fact that the second version works can be explained with the type inference that the compiler does. Basically, it just looks at the argument, sees that it is Map<String, List<String>>, and infers that the T of the method declaration must be String for this to work. You can imagine this as T simply being replaced with String, and then the method matches perfectly. (It's not really so easy, but can intuitively be understood like this)
The reason of why the first version does not work is seemingly simple as well. Coming back to the initial statement: The method expects an object that has a subtype of the type declared in the method signature. And the key point is:
Map<String, List<String>> is not a subtype of Map<String, List<?>>
This fact can (in this case) even be simplified by omitting the method invocation:
Map<String, List<String>> map = new HashMap<>();
Map<String, List<?>> otherMap = map; // Does not work
Again, I'll leave the details of the "type theory" behind that to the FAQ entry linked above, but to at least explain why it is not allowed: A Map<String, List<?>> is a map that maps strings to lists that contain an unknown/unspecified type. So the following implementation of your processMap1 method would be valid:
public void processMap1(Map<String, List<?>> map)
{
List<Number> numbers = new ArrayList<Number>();
map.put("x", numbers);
}
And imagine, calling this method with a Map<String, List<String>> was valid:
public void f() {
Map<String, List<String>> map = new HashMap<>();
processMap1(map); // Imagine this was possible
List<String> shouldBeStrings = map.get("x");
}
then you would end up with a ClassCastException sooner or later, because you pass Map<String, List<String>> to the method, but the method is allowed to put a List<Number> into this map.
Or for short: It is not allowed because it is not type-safe.
Edit in response to the comments (this goes somewhat beyond the original question, so I'll try to keep it short) :
For the second case, there is no T to capture any type. The type is ?, which, intuitively, can stand for any type. In the case of the suggested call from reverse to rev for lists, the T is still ?, but may be used to dedicatedly represent the type of the elements in the list. Or again, referring to the subtype relationships:
List<T> is always a subtype of List<?> (regardless of what T is)
The suggested call from processMap1 to processMap2 does not work for the reasons explained above: Even when Y is a subtype of X, the type Map<String, Y> is not a subtype of Map<String, X>. Here, this question may be relevant.

Typesafe Hetereogeneous Container design pattern with typesafe collections as value

I want to create a typesafe collection which can store multiple collections of the same type but with typesafe parameters. The standart way:
Map<Key<?>, Object> container = new HashMap<>();
The key contains the type of the object and the get method casts to the correct type (standart typesafe hetereogeneous container pattern). But i need something like this:
container.put(Key, new HashMap<Long, String>);
The type itself would be safe (Map.class) but i don't know how to ensure that key and value of the map are of the type long and string. How can i do that with java?
EDIT
To make things clearer:
Map<Class<?>, Object> container = new HashMap<>();
Now the typesafe implementation of this map:
public <T> void put(Class<T> key, T value) {
container.put(key, value);
}
public <T> T get(Class<T> key) {
return key.cast(container.get(key));
}
I can to this in a typesafe way now:
containerClass.put(Double.class, 2.0);
containerClass.put(Integer.class, 3);
And of course:
containerClass.put(MyObject.class, myObject);
If i want to store multiple values of the same type than i could use a generic list instead of the object as a value or a specific key class which has an identifier as a field.
BUT
What happens now?
Map<String, Integer> map = new HashMap<>();
containerClass.put(Map.class, map);
With this implementation it is not safe that it is a map with String as a key and integer as the value. I want to store all kinds of objects and collections but the collections itself must be typesafe too.
Not sure what your Key class is but the following is perhaps what you are looking for, just replace String with your Key class
Map<String, Map<? extends Long, ? extends String>> container = new HashMap<>();
container.put("", new HashMap<Long, String>());
container.put("", new HashMap<Long, Long>());
if you try to compile that code, you will find the first put is okay but the 2nd will not compile due to non compliance to the Map type. That type states only an object that extends Long is accepted as the 1st parameter and the 2nd only accepts objects that extend String.

Java type captures for wildcards in collections

I would like to do this (minimal repro):
String key = "foo";
Object value = new Bar();
if (target instanceof Map<?,?>) {
Map<?,?> map = (Map<?,?>)target;
map.put(key, value);
...
But I am told this:
The method put(capture#25-of ?, capture#26-of ?) in the type
Map is not applicable for the
arguments (String, Object)
It seems like String and Object should both be okay here. My question has two parts: (1) why? And (2) how can I make something like this work?
The problem is that collections that use unbounded wildcards don't allow elements to be added to them. If they did, you could cast the collection to have more specific type parameters, and all of a sudden the type-safety that generics are supposed to offer is gone:
Map<?,?> map = (Map<?,?>)target;
map.put(key, value); // Not actually allowed
Map<String, String> evilMap = (Map<String, String>)map;
String barAsString = evilMap.get(key); // But it's actually a Bar!
Map<?, ?> really means, Map<? extends Object, ? extends Object> According to Java typesystem, you can only get values of type parameters specialized with ? extends wildcard from method calls. You can't pass it to the methods.
P.S. Probably you want Map<Object, Object>
This blog post provides the answer. In short, the compiler doesn't know if Map<?, ?> is really a Map<String, Object> or, say, a Map<Integer, Double>, so it can't guarantee type safety for you.

List<Map<String, String>> vs List<? extends Map<String, String>>

Is there any difference between
List<Map<String, String>>
and
List<? extends Map<String, String>>
?
If there is no difference, what is the benefit of using ? extends?
The difference is that, for example, a
List<HashMap<String,String>>
is a
List<? extends Map<String,String>>
but not a
List<Map<String,String>>
So:
void withWilds( List<? extends Map<String,String>> foo ){}
void noWilds( List<Map<String,String>> foo ){}
void main( String[] args ){
List<HashMap<String,String>> myMap;
withWilds( myMap ); // Works
noWilds( myMap ); // Compiler error
}
You would think a List of HashMaps should be a List of Maps, but there's a good reason why it isn't:
Suppose you could do:
List<HashMap<String,String>> hashMaps = new ArrayList<HashMap<String,String>>();
List<Map<String,String>> maps = hashMaps; // Won't compile,
// but imagine that it could
Map<String,String> aMap = Collections.singletonMap("foo","bar"); // Not a HashMap
maps.add( aMap ); // Perfectly legal (adding a Map to a List of Maps)
// But maps and hashMaps are the same object, so this should be the same as
hashMaps.add( aMap ); // Should be illegal (aMap is not a HashMap)
So this is why a List of HashMaps shouldn't be a List of Maps.
You cannot assign expressions with types such as List<NavigableMap<String,String>> to the first.
(If you want to know why you can't assign List<String> to List<Object> see a zillion other questions on SO.)
What I'm missing in the other answers is a reference to how this relates to co- and contravariance and sub- and supertypes (that is, polymorphism) in general and to Java in particular. This may be well understood by the OP, but just in case, here it goes:
Covariance
If you have a class Automobile, then Car and Truck are their subtypes. Any Car can be assigned to a variable of type Automobile, this is well-known in OO and is called polymorphism. Covariance refers to using this same principle in scenarios with generics or delegates. Java doesn't have delegates (yet), so the term applies only to generics.
I tend to think of covariance as standard polymorphism what you would expect to work without thinking, because:
List<Car> cars;
List<Automobile> automobiles = cars;
// You'd expect this to work because Car is-a Automobile, but
// throws inconvertible types compile error.
The reason of the error is, however, correct: List<Car> does not inherit from List<Automobile> and thus cannot be assigned to each other. Only the generic type parameters have an inherit relationship. One might think that the Java compiler simply isn't smart enough to properly understand your scenario there. However, you can help the compiler by giving him a hint:
List<Car> cars;
List<? extends Automobile> automobiles = cars; // no error
Contravariance
The reverse of co-variance is contravariance. Where in covariance the parameter types must have a subtype relationship, in contravariance they must have a supertype relationship. This can be considered as an inheritance upper-bound: any supertype is allowed up and including the specified type:
class AutoColorComparer implements Comparator<Automobile>
public int compare(Automobile a, Automobile b) {
// Return comparison of colors
}
This can be used with Collections.sort:
public static <T> void sort(List<T> list, Comparator<? super T> c)
// Which you can call like this, without errors:
List<Car> cars = getListFromSomewhere();
Collections.sort(cars, new AutoColorComparer());
You could even call it with a comparer that compares objects and use it with any type.
When to use contra or co-variance?
A bit OT perhaps, you didn't ask, but it helps understanding answering your question. In general, when you get something, use covariance and when you put something, use contravariance. This is best explained in an answer to Stack Overflow question How would contravariance be used in Java generics?.
So what is it then with List<? extends Map<String, String>>
You use extends, so the rules for covariance applies. Here you have a list of maps and each item you store in the list must be a Map<string, string> or derive from it. The statement List<Map<String, String>> cannot derive from Map, but must be a Map.
Hence, the following will work, because TreeMap inherits from Map:
List<Map<String, String>> mapList = new ArrayList<Map<String, String>>();
mapList.add(new TreeMap<String, String>());
but this will not:
List<? extends Map<String, String>> mapList = new ArrayList<? extends Map<String, String>>();
mapList.add(new TreeMap<String, String>());
and this will not work either, because it does not satisfy the covariance constraint:
List<? extends Map<String, String>> mapList = new ArrayList<? extends Map<String, String>>();
mapList.add(new ArrayList<String>()); // This is NOT allowed, List does not implement Map
What else?
This is probably obvious, but you may have already noted that using the extends keyword only applies to that parameter and not to the rest. I.e., the following will not compile:
List<? extends Map<String, String>> mapList = new List<? extends Map<String, String>>();
mapList.add(new TreeMap<String, Element>()) // This is NOT allowed
Suppose you want to allow any type in the map, with a key as string, you can use extend on each type parameter. I.e., suppose you process XML and you want to store AttrNode, Element etc in a map, you can do something like:
List<? extends Map<String, ? extends Node>> listOfMapsOfNodes = new...;
// Now you can do:
listOfMapsOfNodes.add(new TreeMap<Sting, Element>());
listOfMapsOfNodes.add(new TreeMap<Sting, CDATASection>());
Today, I have used this feature, so here's my very fresh real-life example. (I have changed class and method names to generic ones so they won't distract from the actual point.)
I have a method that's meant to accept a Set of A objects that I originally wrote with this signature:
void myMethod(Set<A> set)
But it want to actually call it with Sets of subclasses of A. But this is not allowed! (The reason for that is, myMethod could add objects to set that are of type A, but not of the subtype that set's objects are declared to be at the caller's site. So this could break the type system if it were possible.)
Now here come generics to the rescue, because it works as intended if I use this method signature instead:
<T extends A> void myMethod(Set<T> set)
or shorter, if you don't need to use the actual type in the method body:
void myMethod(Set<? extends A> set)
This way, set's type becomes a collection of objects of the actual subtype of A, so it becomes possible to use this with subclasses without endangering the type system.
As you mentioned, there could be two below versions of defining a List:
List<? extends Map<String, String>>
List<?>
2 is very open. It can hold any object type. This may not be useful in case you want to have a map of a given type. In case someone accidentally puts a different type of map, for example, Map<String, int>. Your consumer method might break.
In order to ensure that List can hold objects of a given type, Java generics introduced ? extends. So in #1, the List can hold any object which is derived from Map<String, String> type. Adding any other type of data would throw an exception.

Categories

Resources