简体   繁体   中英

Java generics How to accept any derived type in generic parameter

In The below 2 lines of code

HashMap<Integer, ?extends Collection<String>> map=
                                           new HashMap<Integer, TreeSet<String>>();

map.put(1,new TreeSet<String>());

Line 2 :The method put(Integer, capture#1-of ? extends Collection) in the type HashMap> is not applicable for the arguments (int, TreeSet)

Line 1: this has no error.

Why is the same generic type (TreeSet< String >) allowed in Line 1 but not allowed in Line 2?

Edit : With super instead of extends ,why is the following is NOT allowed.

HashMap<Integer, ?super Collection<String>> map=new HashMap(<Integer, TreeSet<String>>());

but

HashMap<Integer, ?super Collection<String>> map=new HashMap();
map.put(1,new TreeSet<String>());

is allowed

The reason you get a compiler error is the same reason you cannot add a Dog to a List<? extends Animal> List<? extends Animal> -- you cannot call a method with a generic parameter when the type of the reference variable has an upper bound wildcard. The map variable's value type parameter could refer to any type that matches ? extends Collection<String> ? extends Collection<String> , perhaps as HashMap<Integer, LinkedList<String>> . You could legally insert this line before the call to put :

map = new HashMap<Integer, LinkedList<String>>();

The compiler doesn't know the exact type that's really in the map, so it must, at compile time, prevent you from put ting a TreeSet<String> in as a value to a map whose value could be something like LinkedList<String> .

To put a value in map (besides null ), you must remove the wildcard.

HashMap<Integer, TreeSet<String>> map =
         new HashMap<Integer, TreeSet<String>>();

As JB Nizet has commented, you still can put a value of any Collection such as TreeSet if you remove the wildcard but keep Collection .

HashMap<Integer, Collection<String>> map =
         new HashMap<Integer, Collection<String>>();

(Also, the diamond operator can simplify the declarations here.)

In response to the changes added to the question:

Here, you've used a lower bound.

HashMap<Integer, ? super Collection<String>> map = new HashMap<Integer, TreeSet<String>>());

The type parameter may be Collection<String> or any supertype, such as Object . This disallows subtypes such as TreeSet<String> . Java's generics are invariant. The only reason any variation from Collection<String> is allowed is because of the wildcard.

HashMap<Integer, ? super Collection<String>> map = new HashMap<>();
map.put(1, new TreeSet<String>());

This is allowed because any supertype of Collection<String> will match any subtype as an argument. After all, a TreeSet<String> is an Object . A TreeSet<String> can be put as a value to map , whether it's a HashMap<Integer, Object> or a HashMap<Integer, Collection<String>> , or any type in between. The compiler can prove type safety, so it allows the call.

The declaration of the map tells the compiler that a value inside in the map is some collection of strings. At runtime, it could be a TreeSet but it could also be some other collection type. Hence the compiler cannot allow putting a TreeSet since it may actually contain ArrayList values.

More generally, whenever you use a bound type argument using the ? wildcard, you are practically only allowed to read from the map (eg iterate over its elements). In other words, you can always do:

for(Iterator<? extends Collection<String>> iterator = map.values().iterator(); iterator.hasNext();) {
    Collection<String> collection = iterator.next();
    ... 
}

In your case, however, since you're adding a TreeSet it means that most likely you know that the map will contain TreeSet values, so you don't need to use a wildcard:

HashMap<Integer, TreeSet<String>> map = new HashMap<>(); // In Java 7+, you can use the diamond operator when creating the HashMap

Compiler says:

no suitable method found for put(int,TreeSet) map.put(1, new TreeSet()); method HashMap.put(Integer,CAP#1) is not applicable (actual argument TreeSet cannot be converted to CAP#1 by method invocation conversion) method AbstractMap.put(Integer,CAP#1) is not applicable (actual argument TreeSet cannot be converted to CAP#1 by method invocation conversion) where CAP#1 is a fresh type-variable: CAP#1 extends Collection from capture of ? extends Collection

I think it's because of type ambiguity for compiler and as AR.3 sayed:

The declaration of the map tells the compiler that a value inside in the map is some collection of strings. At runtime, it could be a TreeSet but it could also be some other collection type.

If the type be certain for the compiler the code will compiles and runs without any problem, for instance you can write your code as below which compiles and runs successfully:

public <T extends Collection<String>> void someVoid(){
        //...
        //...
        HashMap<Integer, T > map
                = new HashMap<>();
        map.put(1,(T) (new TreeSet<String>()));
        //...
        //...
    }

After reading the other answers and thinking some more about the original question (which was really about having the " ? extends " part versus not having it), I came up with the following example, which should be easier to understand.

Suppose we have this method:

void doSomething(Collection<? extends Number> numbers) { ... }

And the following usages elsewhere:

Collection<Integer> integers = asList(1, 2, 3);
Collection<Long> longs = asList(1L, 2L, 3L);
doSomething(integers);
doSomething(longs);

All of the above compiles fine. Now, let's ask, what can the doSomething method do with the collection it receives? Can it add some number to it? The answer is no , it can't. And that's because it would be unsafe; for example, if it added an Integer (which is a Number ), then the doSomething(longs) call would fail. So, to prevent such possibilities the compiler disallows any attempt to add any Number at all to a Collection<? extends Number> Collection<? extends Number> .

Similarly, what if the method was declared as:

void doSomething(Collection<Number> numbers) { ... }

In this case, the method can add any number to the collection, but then calls like doSomething(integers) become compiler errors, since a collection of Integer can only accept integers. So, again, the compiler prevents the possiblity for ClassCastException s later, ensuring the code is type safe.

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