Below code where (? extends aClass) is used has confused me [duplicate] - java

This question already has answers here:
What is PECS (Producer Extends Consumer Super)?
(16 answers)
Closed 3 years ago.
As per my understanding(which needs correction obviously) the map should take Integer class and all of its sub classes. and same with Location class.
Map<? extends Integer, ? extends Location> test2 = new HashMap<>();
test2.put(new Integer(5), new Location(1, "Test2", exits));
I have watched and read from many different resources. But I still can't get my head around this.
I am not a professional programmer.

Quoting Oracle Docs for generics
You can use an upper bounded wildcard to relax the restrictions on a variable. For example, say you want to write a method that works on List<Integer>, List<Double>, and List<Number>; you can achieve this by using an upper bounded wildcard.
and
The upper bounded wildcard, <? extends Foo>, where Foo is any type, matches Foo and any subtype of Foo. The process method can access the list elements as type Foo:
So basically test2 is a Map which takes the key as Integer or any class which has extended Integer(i.e subclasses of Integer) and the value as Location or its subclass

This can be explained better with small example, let's take a method with generic argument of Map
public static void m1(Map<? extends Number, ? extends Object> test2) {
}
Now you can create Map objects with key as Number or it's child classes and value as Object or it child classes and call that method
Map<Integer, String> map1 = new HashMap<Integer, String>();
m1(map1);
Map<Double, String> map2 = new HashMap<Double, String>();
m1(map2);
Map<Number, StringBuffer> map3 = new HashMap<Number, StringBuffer>();
m1(map3);
Map<Integer, StringBuilder> map4 = new HashMap<Integer, StringBuilder>();
m1(map4);
? extends Number doesn't mean map can contains Number or it's child object, it means either you can create Map with key as Number or it's child's. which is the same for value also ? extends Object either Object or it's child's

Related

JAVA: List<List<? extends Object>> vs List<List<Object>> [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.

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.

Java HashMap nested generics with wildcards

I'm trying to make a hashmap of hashmap values containing hashsets of different subclasses of a custom class, like so:
HashMap<String, Hashmap<String, HashSet<? extends AttackCard>>> superMap
AttackCard has subclasses such as: Mage, Assassin, Fighter. Each HashMap in the superMap will only ever have HashSets containing a single subclass of AttackCard.
When I try putting a
HashMap<String, HashSet<Assassin>>
into superMap, I get a compiler error:
below is the code where the error occurs:
public class CardPool {
private HashMap<String, HashMap<String, HashSet<? extends AttackCard>>> attackPool =
new HashMap<>();
private ArrayList<AuxiliaryCard> auxiliaryPool;
public CardPool() {
(line 24)this.attackPool.put("assassins", new AssassinPool().get());
/* this.attackPool.put("fighters", new Fighter().getPool());
this.attackPool.put("mages", new Mage().getPool());
this.attackPool.put("marksmen", new Marksman().getPool());
this.attackPool.put("supports", new Support().getPool());
this.attackPool.put("tanks", new Tank().getPool());
*/
this.auxiliaryPool = new ArrayList<>(new AuxiliaryCard().getPool());
}
And here is a snippet of the AssassinPool get-method:
private HashMap<String, HashSet<Assassin>> pool = new HashMap<>();
public HashMap<String, HashSet<Assassin>> get() {
return pool;
}
I'd like to comment that I could easily solve my problem and have a wonderfully working program by making all the AttackCardPools, such as AssassinPool, return and contain HashSets of AttackCard instead of their respective subclass. I'm trying to understand this compilation error, however :)
compilation error at line 24: error: no suitable method found for `put(String, HashMap<String,HashSet<Assassin>>>`
this.attackPool.put("assassins", new AssassinPool(). get());
method HashMap.putp.(String, HashMap<String,HashSet<? extends AttackCard>>>` is not applicable (actual argument `HashMap<String, HashSet<Assassin>>` cannot be converted to `HashMap<String, HashSet<? extends AttackCard>>` by method invocation conversion)
Multi-level wildcards can be a bit tricky at times, when not dealt with properly. You should first learn how to read a multi-level wildcards. Then you would need to learn to interpret the meaning of extends and super bounds in multi-level wildcards. Those are important concepts that you must first learn before starting to use them, else you might very soon go mad.
Interpreting a multi-level wildcard:
**Multi-level wildcards* should be read top-down. First read the outermost type. If that is yet again a paramaterized type, go deep inside the type of that parameterized type. The understanding of the meaning of concrete parameterized type and wildcard parameterized type plays a key role in understand how to use them. For example:
List<? extends Number> list; // this is wildcard parameterized type
List<Number> list2; // this is concrete parameterized type of non-generic type
List<List<? extends Number>> list3; // this is *concrete paramterized type* of a *wildcard parameterized type*.
List<? extends List<Number>> list4; // this is *wildcard parameterized type*
First 2 are pretty clear.
Take a look at the 3rd one. How would you interpret that declaration? Just think, what type of elements can go inside that list. All the elements that are capture-convertible to List<? extends Number>, can go inside the outer list:
List<Number> - Yes
List<Integer> - Yes
List<Double> - Yes
List<String> - NO
References:
JLS §5.1.10 - Capture Conversion
Java Generics FAQs - Angelika Langer
Wildcard Capture
IBM Developer Works Article - Understanding Wildcard Captures
Given that the 3rd instantiation of list can hold the above mentioned type of element, it would be wrong to assign the reference to a list like this:
List<List<? extends Number>> list = new ArrayList<List<Integer>>(); // Wrong
The above assignment should not work, else you might then do something like this:
list.add(new ArrayList<Float>()); // You can add an `ArrayList<Float>` right?
So, what happened? You just added an ArrayList<Float> to a collection, which was supposed to hold a List<Integer> only. That will certainly give you trouble at runtime. That is why it's not allowed, and compiler prevents this at compile time only.
However, consider the 4th instantiation of multi-level wildcard. That list represents a family of all instantiation of List with type parameters that are subclass of List<Number>. So, following assignments are valid for such lists:
list4 = new ArrayList<Integer>();
list4 = new ArrayList<Double>();
References:
What do multi-level wildcards mean?
Difference between a Collection<Pair<String,Object>>, a Collection<Pair<String,?>> and a Collection<? extends Pair<String,?>>?
Relating to single-level wildcard:
Now this might be making a clear picture in your mind, which relates back to the invariance of generics. A List<Number> is not a List<Double>, although Number is superclass of Double. Similarly, a List<List<? extends Number>> is not a List<List<Integer>> even though the List<? extends Number> is a superclass of List<Integer>.
Coming to the concrete problem:
You have declared your map as:
HashMap<String, Hashmap<String, HashSet<? extends AttackCard>>> superMap;
Note that there is 3-level of nesting in that declaration. Be careful. It's similar to List<List<List<? extends Number>>>, which is different from List<List<? extends Number>>.
Now what all element type you can add to the superMap? Surely, you can't add a HashMap<String, HashSet<Assassin>> into the superMap. Why? Because we can't do something like this:
HashMap<String, HashSet<? extends AttackCard>> map = new HashMap<String, HashSet<Assassin>>(); // This isn't valid
You can only assign a HashMap<String, HashSet<? extends AttackCard>> to map and thus only put that type of map as value in superMap.
Option 1:
So, one option is to modify your last part of the code in Assassin class(I guess it is) to:
private HashMap<String, HashSet<? extends AttackCard>> pool = new HashMap<>();
public HashMap<String, HashSet<? extends AttackCard>> get() {
return pool;
}
... and all will work fine.
Option 2:
Another option is to change the declaration of superMap to:
private HashMap<String, HashMap<String, ? extends HashSet<? extends AttackCard>>> superMap = new HashMap<>();
Now, you would be able to put a HashMap<String, HashSet<Assassin>> to the superMap. How? Think of it. HashMap<String, HashSet<Assassin>> is capture-convertible to HashMap<String, ? extends HashSet<? extends AttackCard>>. Right? So the following assignment for the inner map is valid:
HashMap<String, ? extends HashSet<? extends AttackCard>> map = new HashMap<String, HashSet<Assassin>>();
And hence you can put a HashMap<String, HashSet<Assassin>> in the above declared superMap. And then your original method in Assassin class would work fine.
Bonus Point:
After solving the current issue, you should also consider to change all the concrete class type reference to their respective super interfaces. You should change the declaration of superMap to:
Map<String, Map<String, ? extends Set<? extends AttackCard>>> superMap;
So that you can assign either HashMap or TreeMap or LinkedHashMap, anytype to the superMap. Also, you would be able to add a HashMap or TreeMap as values of the superMap. It's really important to understand the usage of Liskov Substitution Principle.
Don't use HashSet<? extends AttackCard>, just use HashSet<AttackCard> in all declarations - the superMap and all Sets being added.
You can still store subclasses of AttackCard in a Set<AttackCard>.
You should be declaring your variables using the abstract type, not the concrete implantation, ie:
Map<String, Map<String, Set<? extends AttackCard>>> superMap
See Liskov substitution principle
Probably a question of covariance, you need to replace ? extends by ? super.
See What is PECS (Producer Extends Consumer Super)?

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.

Why does this simple Java generic function not compile?

While f1 does compile, the very similar f2 won't and I just cant explain why.
(Tested on Intellij 9 and Eclipse 3.6)
And really I thought I was done with that kind of question.
import java.util.*;
public class Demo {
public List<? extends Set<Integer>> f1(){
final List<HashSet<Integer>> list = null;
return list;
}
public List<List<? extends Set<Integer>>> f2(){
final List<List<HashSet<Integer>>> list = null;
return list;
}
}
List<List<HashSet<Integer>>> is not assignable to List<List<? extends Set<Integer>>> for the same reason List<HashSet<Integer>> would not be assignable to List<Set<Integer>>.
You can get it to compile by changing this:
public List<List<? extends Set<Integer>>> f2(){
into this:
public List<? extends List<? extends Set<Integer>>> f2(){
The reason your code didn't compile, and why the other example I gave (ie: "List<HashSet<Integer>> would not be assignable to List<Set<Integer>>") is that Java generics are not covariant.
The canonical example is that even if Circle extends Shape, List<Circle> does not extend List<Shape>. If it did, then List<Circle> would need to have an add(Shape) method that accepts Square objects, but obviously you don't want to be able to add Square objects to a List<Circle>.
When you use a wildcard, you're getting a type that slices away certain methods. List<? extends Shape> retains the methods that return E, but it doesn't have any of the methods that take E as a parameter. This means you still have the E get(int) method, but add(E) is gone. List<? extends Shape> is a super-type of List<Shape> as well as List<Circle>, List<? extends Circle>, etc. (? super wildcards slice the other way: methods that return values of the type parameter are removed)
Your example is more complicated because it has nested type parameters, but it boils down to the same thing:
List<HashSet<Integer>> is a sub-type of List<? extends Set<Integer>>
Because generics are not covariant, wrapping the two types in a generic type (like List<...>) yields a pair of types that no longer have the sub/super-type relationship. That is, List<List<HashSet<Integer>>> is not a sub-type of List<List<? extends Set<Integer>>>
If instead of wrapping with List<...> you wrap with List<? extends ...> you'll end up with the original relationship being preserved. (This is just a rule of thumb, but it probably covers 80% of the cases where you'd want to use wildcards.)
Note that trashgod and BalusC are both correct in that you probably don't want to be returning such a weird type. List<List<Set<Integer>>> would be a more normal return type to use. That should work fine as long as you're consistent about always using the collection interfaces rather than the concrete collection classes as type parameters. eg: you can't assign a List<ImmutableSet<Integer>> to a List<Set<Integer>>, but you can put ImmutableSet<Integer> instances into a List<Set<Integer>>, so never say List<ImmutableSet<Integer>>, say List<Set<Integer>>.
"Do not use wildcard types as return types. Rather than providing additional flexibility for your users, it would force them to use wildcard types in client code."—Joshua Bloch, Effective Java Second Edition, Chapter 5, Item 28.
This is too long to fit in a comment. I just wanted to say that it makes no sense to declare it that way. You can also just do the following:
public List<List<Set<Integer>>> f2() {
List<List<Set<Integer>>> list = new ArrayList<List<Set<Integer>>>();
List<Set<Integer>> nestedList = new ArrayList<Set<Integer>>();
list.add(nestedList);
Set<Integer> set = new HashSet<Integer>();
nestedList.add(set);
return list;
}
Works as good. I see no point of using ? extends SomeInterface here.
Update: as per the comments, you initially wanted to solve the following problem:
public List<Map<Integer, Set<Integer>>> getOutcomes() {
Map<HashSet<Integer>, Integer> map = new HashMap<HashSet<Integer>, Integer>();
List<Map<Integer, Set<Integer>>> outcomes = new ArrayList<Map<Integer, Set<Integer>>>();
for (Map.Entry<HashSet<Integer>, Integer> entry : map.entrySet()) {
outcomes.add(asMap(entry.getValue(), entry.getKey()));
// add() gives compiler error: The method add(Map<Integer,Set<Integer>>)
// in the type List<Map<Integer,Set<Integer>>> is not applicable for
// the arguments (Map<Integer,HashSet<Integer>>)
}
return outcomes;
}
public <K, V> Map<K, V> asMap(K k, V v) {
Map<K, V> result = new HashMap<K, V>();
result.put(k, v);
return result;
}
This can just be solved by declaring the interface (Set in this case) instead of implementation (HashSet in this case) as generic type. So:
public List<Map<Integer, Set<Integer>>> getOutcomes() {
Map<Set<Integer>, Integer> map = new HashMap<Set<Integer>, Integer>();
List<Map<Integer, Set<Integer>>> outcomes = new ArrayList<Map<Integer, Set<Integer>>>();
for (Map.Entry<Set<Integer>, Integer> entry : map.entrySet()) {
outcomes.add(asMap(entry.getValue(), entry.getKey()));
// add() now compiles fine.
}
return outcomes;
}
In future problems, try to ask how to solve a particular problem, not how to achieve a particular solution (which in turn may not be the right solution after all).

Categories

Resources