131

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?

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Eng.Fouad
  • 115,165
  • 71
  • 313
  • 417

5 Answers5

185

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.

trutheality
  • 23,114
  • 6
  • 54
  • 68
  • 6
    Still, `HashMap` is a `Map` due to the polymorphism. – Eng.Fouad Mar 21 '12 at 18:22
  • 47
    Right, but a `List` of `HashMap`s isn't a `List` of `Map`s. – trutheality Mar 21 '12 at 18:26
  • 1
    This is called [Bounded quantification](http://en.wikipedia.org/wiki/Bounded_quantification) – Dan Burton Mar 22 '12 at 02:28
  • Good example. Also worth noting is that even if you declare `List> maps = hashMaps; ` and `HashMap aMap = new HashMap();`, you will still find that `maps.add(aMap);` is illegal while `hashMaps.add(aMap);` is legal. The purpose is to prevent addition of wrong types, but it won't allow addition of right types (compiler can't determine the "right" type during compile time) – Raze Mar 22 '12 at 05:23
  • @Raze no, actually you *can* add a `HashMap` to a list of `Map`s, both of your examples are legal, if I'm reading them correctly. – trutheality Mar 22 '12 at 18:20
  • @trutheality, oops! I meant to say `List extends Map> maps = ...`!!! – Raze Mar 23 '12 at 01:55
  • @Raze Ah, yes. The general rule I like to remember is that you don't add to ` extends ...>` collections and don't read from ` super ...>` collections. – trutheality Mar 23 '12 at 16:00
  • @trutheality 28 up-votes left to get the great answer badge ;) – Eng.Fouad Mar 25 '12 at 11:48
  • @trutheality congrats on getting the gold great answer badge :) – Eng.Fouad Mar 27 '12 at 18:45
24

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

Tom Hawtin - tackline
  • 145,806
  • 30
  • 211
  • 305
  • 1
    can explain further it ? i cant understand or any link for good practice? – Samir Mangroliya Mar 21 '12 at 18:23
  • 3
    @Samir Explain what? `List` is not a subtype of `List`? - see, for example, http://stackoverflow.com/questions/3246137/java-generics-cannot-cast-listsubclass-to-listsuperclass – Tom Hawtin - tackline Mar 21 '12 at 18:29
  • 2
    This doesn't explain what the difference is between with or without `? extends`. Nor does it explain the correlation with super/subtypes or co/contravariance (if there is any). – Abel Mar 28 '12 at 07:14
18

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>());
Community
  • 1
  • 1
Abel
  • 56,041
  • 24
  • 146
  • 247
  • Nothing in "So what is it then with..." will compile. – NobleUplift Aug 09 '13 at 21:48
  • @NobleUplift: it's hard to help you out if you don't supply the error message you get. Consider also, as an alternative, to ask a new question on SO to get your answer, larger chance of success. The code above are just snippets, it depends you implemented it in your scenario. – Abel Aug 12 '13 at 18:36
  • I don't have a new question, I have improvements. `List extends Map> mapList = new ArrayList extends Map>(); mapList.add(new TreeMap());` results in `found: ? extends java.util.Map required: class or interface without bounds`. `List> mapList = new ArrayList>(); mapList.add(new TreeMap());` works perfectly. The last example is obviously correct. – NobleUplift Aug 13 '13 at 19:15
  • @NobleUplift: sorry, travelling long time, will fix when I'm back, and thanks for pointing out the glaring error! :) – Abel Sep 17 '13 at 11:02
  • No problem, I'm just glad it will be fixed. I made the edits for you if you want to approve them. – NobleUplift Sep 17 '13 at 16:06
4

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.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Rörd
  • 6,556
  • 21
  • 27
0

As you mentioned, there could be two below versions of defining a List:

  1. List<? extends Map<String, String>>
  2. 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.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Java Spring Coder
  • 1,017
  • 3
  • 12
  • 20