简体   繁体   中英

Why HashMap with generic declaration “<? super ArrayList> does not accept value ”new Object()" in the put method?

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> < ? super ArrayList> ; it means I can add any value that is higher than ArrayList (ie 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?

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());

? super ArrayList ? 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.

The type ? super ArrayList ? 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.

? super ArrayList ? 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 .

Map<Object, ? super ArrayList> m 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

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 ? super E .

If the Collection should be able to 'produce' E , ( E can be retrieved from it), declare the generic as ? extends E ? 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.

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

EDIT : after the down vote, I tried out to understand if I misunderstood something. 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.

when you type

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

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

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.

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM