12

While working on interview questions, I have come across below code:

List<Object> list = new ArrayList();
Map<Object, ? super ArrayList> m = new HashMap<Object,  ArrayList>();
m.put(1, new Object());
m.put(2, list);

The above two put method's are throwing compile time error. But, when I add m.put(3, new ArrayList()); it is adding to map with no compile time error.

It is very clear for me that I can add new Object() as a value in HashMap because of the map declaration to be of type < ? super ArrayList>; it means I can add any value that is higher than ArrayList (i.e. super of ArrayList) and ArrayList object too, but not anything below ArrayList.

This particular concept is very well written in SCJP 6 by Kathy Sierra and Bert Bates and based on that theory and examples I assumed it should do job as I understood. Can someone help me understand the error?

gnat
  • 6,213
  • 108
  • 53
  • 73
MKod
  • 803
  • 5
  • 20
  • 33
  • I added another answer after finally getting my head around how this works, because I still believe none of the other answers really explain it properly. – wvdz Jan 04 '15 at 13:37

10 Answers10

8

You are misunderstanding what the wildcard ? means. You probably have a common misconception:

It does not mean that you can put any kind of object in the map which is of a type that is a superclass of ArrayList.

What it means is that the values in the map are of some unknown type that is a supertype of ArrayList. Since the exact type is not known, the compiler won't allow you to put a value of any type other than ArrayList itself as a value in the map - the compiler does not have enough information to check if what you are doing is type-safe.

Suppose that this was allowed, then you could do bad things like this:

Map<Object, ArrayList> m1 = new HashMap<Object, ArrayList>();

Map<Object, ? super ArrayList> m2 = m1;

// This should not be allowed, because m2 is really a HashMap<Object, ArrayList>
m2.put(1, new Object());
Jesper
  • 202,709
  • 46
  • 318
  • 350
  • I still don't understand it. From what you say here it seems there is not even a usecase for the super generic keyword. – wvdz Jan 03 '15 at 12:07
  • @Jesper is wrong, ` super X>` means X or bigger; check out my answer with the newly added sample. – ThanksForAllTheFish Jan 03 '15 at 12:11
  • 3
    (At computer now)...there is a huge use case for the super keyword. Check Joshua Bloch's book Effective Java (2nd Edition) where he describes PECS, which stands for "Producer Extends, Consumer Super". This SO answer may help provider more insight: http://stackoverflow.com/questions/2723397/java-generics-what-is-pecs – lostdorje Jan 03 '15 at 12:30
  • This answer is basically the one-eyed king in the land of the blind. Of all the terrible answers to this question, this may be the least terrible, but I still doesn't offer a proper explanation of how the super generics keyword works (and doesn't work). – wvdz Jan 03 '15 at 15:50
  • 1
    @popovitsj: That's because that's not what the question asks for. This answer exactly answers the question as posed. – Oliver Charlesworth Jan 03 '15 at 17:13
3

? super ArrayList means "I don't know exactly what type this is but I do know that anywhere that requires me to provide an instance of this type I can safely give it an ArrayList". The actual type that instantiates the wildcard might be Object, Collection, List or ArrayList, so you can see that new Object() can't be guaranteed to be safe but new ArrayList() can.

Ian Roberts
  • 120,891
  • 16
  • 170
  • 183
3

The type ? super ArrayList means an unknown type, which has a lower bound of ArrayList. For example, it could be Object, but might be AbstractList or ArrayList.

The only thing the compiler can know for sure is that the type of the value, although unknown, is guaranteed to be no more specific than ArrayList, so any object that is an ArrayList, or a subclass of ArrayList, may be added.

Also remember: the compiler only goes by the declared type; it ignores the assigned type.

Why put(1, new Object()) fails:

Clearly, Object is not within bounds.

Why put(1, list) fails:

The variable is of type List, which may hold a reference to a LinkedList, which is not within the required bounds.

Community
  • 1
  • 1
Bohemian
  • 412,405
  • 93
  • 575
  • 722
  • "so any object that is an ArrayList, or a subclass of ArrayList, may be added." So what's the difference with `? extends ArrayList`? – wvdz Jan 04 '15 at 11:21
  • 1
    ` extends ArrayList>` means you don't know what type it is, but you know the type you'll *get* from the object is at least as specific as ArrayList (ie ArrayList or a subtype of ArrayList), and you can't *add* anything because you can't know which subclass of ArrayList the type might be. See [PECS](http://stackoverflow.com/q/2723397/256196) – Bohemian Jan 04 '15 at 11:50
  • I just can't seem to get my head around it. I'm just gonna get my hands on a copy of "Effective Java"... – wvdz Jan 04 '15 at 12:07
  • @popovitsj If you have a `List extends T>` then the List used to be at most a `List` and maybe some subclass of `T`. If you have a `List super T>` then the List used to be at least a `List` and maybe some superclass of `T`. The bound tells you something about the generic parameter that used to be there. – Radiodef Jan 04 '15 at 20:08
  • @Radiodef yeah, I finally understand now, I even added my own answer, as if there weren't enough already :p – wvdz Jan 04 '15 at 20:35
2

? super ArrayList does not mean "any value that is higher than ArrayList". It means "any value of some unknown type ?, which is a supertype of ArrayList". You can see that in your code, actually, because m's values are of type ArrayList:

HashMap<Object,  ArrayList> hm = new HashMap<Object,  ArrayList>();
Map<Object, ? super ArrayList> m = hm;
m.put(1, new Object());
ArrayList a = hm.get(1);

Something is clearly wrong with this code, because a should be an ArrayList, not an Object.

lmm
  • 17,386
  • 3
  • 26
  • 37
2

Map<Object, ? super ArrayList> m can be reference for many maps, like

  • HashMap<Object, ArrayList>
  • HashMap<Object, List>
  • HashMap<Object, Object>

but these maps have their own purpose, which is to hold precisely these types

  • ArrayList
  • List
  • Object

or their subtypes, but never their super-types because super-types may not have all necessary members (methods/fields) which their subtypes have.

Consider this example

List<Banana> bananas = new ArrayList<>();//list only for bananas    <------+
List<? super Banana> list = bananas;//this is fine                         |
//                                                                         |
list.add(new Object);//error, and can thank compiler for that              |
                     //because you just tried to add Object to this list --+
                     //which should contain only Bananas
list.add(new Banana());// this is FINE because whatever precise type of list
                       // it can accept also banana

To illustrate last comment lets use

List<Fruit> fruits = new ArrayList<>();//can contain Bananas, Apples, and so on
List<? super Banana> list = fruits;//again, this is fine                         
list.add(new Object)//wrong, as explained earlier
list.add(new Banana());// OK because we know that list will be of type which
                       // can also accept Banana
Pshemo
  • 122,468
  • 25
  • 185
  • 269
2

I believe none of the answers gives a truly satisfactory explanation. I had to "run to the library" and read Chapter 5 of Effective Java by Joshua Bloch to at last understand how this works.

This question is somewhat complicated by using Map and a raw ArrayList. This doesn't contribute to making the question clear. Basically the question is about what the following generic declarations mean and how they differ:

Collection<? super E> x;
Collection<? extends E> y;

x = a collection of an unknown type that is E or a superclass of E.

y = a collection of an unknown type that is E or a subclass of E.

Usecase for x: E or any subclass of E can be added to it.

Usecase for y: E or any subclass of E can be retrieved from it.

This is also known under the acronym PECS: Producer-extends, Consumer-supers.

If the Collection should be able to 'consume' E (E can be added to it), declare the generic as ? super E.

If the Collection should be able to 'produce' E, (E can be retrieved from it), declare the generic as ? extends E.

I hope this clarifies why the code in the question doesn't compile: only ArrayList or a subtype of ArrayList can be added to the map.

wvdz
  • 16,251
  • 4
  • 53
  • 90
1

You can reference to java specification, chapter 4.5.1. Type Arguments and Wildcards; it states:

Unlike ordinary type variables declared in a method signature, no type inference is required when using a wildcard. Consequently, it is permissible to declare lower bounds on a wildcard, using the following syntax, where B is a lower bound:

? super B

Lower bound means that a valid types are everything "bigger or equal" than B (in your case ArrayList). Neither Object nor List are bigger than ArrayList that is why you get a compile error.

To further test this, try declaring a new custom type like public class MyArrayList extends ArrayList and use it in your Map: the second put will work.

Or declare the map as Map<Object, ? super List>: again, the second put will work.

No way you can put an Object in such map however: Object is the root of java class type, thus is "smaller" than everything.

This sample compiles (with the standard javac):

import java.util.*;

public class test {
  public static void main(String[] args) {
    ArrayList list = new ArrayList();
    Map<Object, ? super List> m = new HashMap<Object, List>(); 
    m.put(2, list);
  }
}

Thus, lower bound actually really means you can put everything bigger or equal.

miken32
  • 42,008
  • 16
  • 111
  • 154
ThanksForAllTheFish
  • 7,101
  • 5
  • 35
  • 54
1

when you type

Map<Object, ? super ArrayList> m = new HashMap<Object,
    ArrayList>();

your "?" become ArrayList so you cannot add nothing besides extends ArrayList

InsFi
  • 1,298
  • 3
  • 14
  • 29
1

Generics exist to provide this behavior. You limit the map entries with generic. Your upper limit is ArrayList but lower limit is open with any subclass of ArrayList. So you can not put the Object entries into this map.

Ali Katkar
  • 517
  • 4
  • 7
0

What you are trying to say is correct. When you are just dry-running the code it makes sense that everything which is a SuperClass of ArrayList can be put as a value in Map.

But the problem is it is by-design. The generic checking is simply at compile-time not at run-time. So at compile time though you know Object is the superclass of ArrayList; compiler doesn't know that. So compiler will complain about it. The only thing that can be put as value is null or ArrayList object itself.

Community
  • 1
  • 1
Anirban Nag 'tintinmj'
  • 5,572
  • 6
  • 39
  • 59