Java HashMap nested generics with wildcards - java

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)?

Related

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

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

Java - Is "extends Object" really necessary on some Collections method specifications? [duplicate]

I've seen the wildcard used before to mean any object - but recently saw a use of:
<? extends Object>
Since all objects extend Object, are these two usages synonymous?
<?> and <? extends Object> are synonymous, as you'd expect.
There are a few cases with generics where extends Object is not actually redundant. For example, <T extends Object & Foo> will cause T to become Object under erasure, whereas with <T extends Foo> it will become Foo under erasure. (This can matter if you're trying to retain compatibility with a pre-generics API that used Object.)
Source: http://download.oracle.com/javase/tutorial/extra/generics/convert.html; it explains why the JDK's java.util.Collections class has a method with this signature:
public static <T extends Object & Comparable<? super T>> T max(
Collection<? extends T> coll
)
Although <?> is supposed to be a shortcut for <? extend object>, there is a tiny difference between the two.
<?> is reifiable while <? extend object> is not. The reason they did this is to make it easier to distinguish reifiable type. Anything that looks like <? extends something>,<T>,<Integer> are nonreifiable.
For example, this code would work
List aList = new ArrayList<>();
boolean instanceTest = aList instanceof List<?>;
but this gives an error
List aList = new ArrayList<>();
boolean instancetest = aList instanceof List<? extends Object>;
for more info read Java generics and collections by Maurice Naftalin
<?> is a shorthand for <? extends Object>.
You may read below shared link for more details.
<?>
"?" denotes any unknown type, It can represent any Type at in code for. Use this wildcard if you are not sure about Type.
ArrayList<?> unknownList = new ArrayList<Number>(); //can accept of type Number
unknownList = new ArrayList<Float>(); //Float is of type Number
Note: <?> means anythings. So It can accept of Type which are not inherited from Object class.
<? extends Object>
<? extends Object> means you can pass an Object or a sub-class that extends Object class.
ArrayList<? extends Number> numberList = new ArrayList<Number>(); //Number of subclass
numberList = new ArrayList<Integer>(); //Integer extends Number
numberList = new ArrayList<Float>(); // Float extends Number
T – used to denote type
E – used to denote element
K – keys
V - values
N – for numbersRef:

Java nested generic type

How come one must use the generic type Map<?, ? extends List<?>> instead of a simpler Map<?, List<?>> for the following test() method?
public static void main(String[] args) {
Map<Integer, List<String>> mappy =
new HashMap<Integer, List<String>>();
test(mappy);
}
public static void test(Map<?, ? extends List<?>> m) {}
// Doesn't compile
// public static void test(Map<?, List<?>> m) {}
Noting that the following works, and that the three methods have the same erased type anyways.
public static <E> void test(Map<?, List<E>> m) {}
Fundamentally, List<List<?>> and List<? extends List<?>> have distinct type arguments.
It's actually the case that one is a subtype of the other, but first let's learn more about what they mean individually.
Understanding semantic differences
Generally speaking, the wildcard ? represents some "missing information". It means "there was a type argument here once, but we don't know what it is anymore". And because we don't know what it is, restrictions are imposed on how we can use anything that refers to that particular type argument.
For the moment, let's simplify the example by using List instead of Map.
A List<List<?>> holds any kind of List with any type argument. So i.e.:
List<List<?>> theAnyList = new ArrayList<List<?>>();
// we can do this
theAnyList.add( new ArrayList<String>() );
theAnyList.add( new LinkedList<Integer>() );
List<?> typeInfoLost = theAnyList.get(0);
// but we are prevented from doing this
typeInfoLost.add( new Integer(1) );
We can put any List in theAnyList, but by doing so we have lost knowledge of their elements.
When we use ? extends, the List holds some specific subtype of List, but we don't know what it is anymore. So i.e.:
List<? extends List<Float>> theNotSureList =
new ArrayList<ArrayList<Float>>();
// we can still use its elements
// because we know they store Float
List<Float> aFloatList = theNotSureList.get(0);
aFloatList.add( new Float(1.0f) );
// but we are prevented from doing this
theNotSureList.add( new LinkedList<Float>() );
It's no longer safe to add anything to the theNotSureList, because we don't know the actual type of its elements. (Was it originally a List<LinkedList<Float>>? Or a List<Vector<Float>>? We don't know.)
We can put these together and have a List<? extends List<?>>. We don't know what type of List it has in it anymore, and we don't know the element type of those Lists either. So i.e.:
List<? extends List<?>> theReallyNotSureList;
// these are fine
theReallyNotSureList = theAnyList;
theReallyNotSureList = theNotSureList;
// but we are prevented from doing this
theReallyNotSureList.add( new Vector<Float>() );
// as well as this
theReallyNotSureList.get(0).add( "a String" );
We've lost information both about theReallyNotSureList, as well as the element type of the Lists inside it.
(But you may note that we can assign any kind of List holding Lists to it...)
So to break it down:
// ┌ applies to the "outer" List
// ▼
List<? extends List<?>>
// ▲
// └ applies to the "inner" List
The Map works the same way, it just has more type parameters:
// ┌ Map K argument
// │ ┌ Map V argument
// ▼ ▼
Map<?, ? extends List<?>>
// ▲
// └ List E argument
Why ? extends is necessary
You may know that "concrete" generic types have invariance, that is, List<Dog> is not a subtype of List<Animal> even if class Dog extends Animal. Instead, the wildcard is how we have covariance, that is, List<Dog> is a subtype of List<? extends Animal>.
// Dog is a subtype of Animal
class Animal {}
class Dog extends Animal {}
// List<Dog> is a subtype of List<? extends Animal>
List<? extends Animal> a = new ArrayList<Dog>();
// all parameterized Lists are subtypes of List<?>
List<?> b = a;
So applying these ideas to a nested List:
List<String> is a subtype of List<?> but List<List<String>> is not a subtype of List<List<?>>. As shown before, this prevents us from compromising type safety by adding wrong elements to the List.
List<List<String>> is a subtype of List<? extends List<?>>, because the bounded wildcard allows covariance. That is, ? extends allows the fact that List<String> is a subtype of List<?> to be considered.
List<? extends List<?>> is in fact a shared supertype:
List<? extends List<?>>
╱ ╲
List<List<?>> List<List<String>>
In review
Map<Integer, List<String>> accepts only List<String> as a value.
Map<?, List<?>> accepts any List as a value.
Map<Integer, List<String>> and Map<?, List<?>> are distinct types which have separate semantics.
One cannot be converted to the other, to prevent us from doing modifications in an unsafe way.
Map<?, ? extends List<?>> is a shared supertype which imposes safe restrictions:
Map<?, ? extends List<?>>
╱ ╲
Map<?, List<?>> Map<Integer, List<String>>
How the generic method works
By using a type parameter on the method, we can assert that List has some concrete type.
static <E> void test(Map<?, List<E>> m) {}
This particular declaration requires that all Lists in the Map have the same element type. We don't know what that type actually is, but we can use it in an abstract manner. This allows us to perform "blind" operations.
For example, this kind of declaration might be useful for some kind of accumulation:
static <E> List<E> test(Map<?, List<E>> m) {
List<E> result = new ArrayList<E>();
for(List<E> value : m.values()) {
result.addAll(value);
}
return result;
}
We can't call put on m because we don't know what its key type is anymore. However, we can manipulate its values because we understand they are all List with the same element type.
Just for kicks
Another option which the question does not discuss is to have both a bounded wildcard and a generic type for the List:
static <E> void test(Map<?, ? extends List<E>> m) {}
We would be able to call it with something like a Map<Integer, ArrayList<String>>. This is the most permissive declaration, if we only cared about the type of E.
We can also use bounds to nest type parameters:
static <K, E, L extends List<E>> void(Map<K, L> m) {
for(K key : m.keySet()) {
L list = m.get(key);
for(E element : list) {
// ...
}
}
}
This is both permissive about what we can pass to it, as well as permissive about how we can manipulate m and everything in it.
See also
"Java Generics: What is PECS?" for the difference between ? extends and ? super.
JLS 4.10.2. Subtyping among Class and Interface Types and JLS 4.5.1. Type Arguments of Parameterized Types for entry points to the technical details of this answer.
This is because the subclassing rules for generics are slightly different from what you may expect. In particular if you have:
class A{}
class B extends A{}
then
List<B> is not a subclass of List<A>
It's explained in details here and the usage of the wildcard (the "?" character) is explained here.

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