Java generics How to accept any derived type in a generic parameter

In the following 2 lines of code

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

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

Line 1: this has no error.

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

Edit: With a super, not an extension, why the following is not valid.

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

allowed

+5
source share
4 answers

The reason you get a compiler error, for the same reason you cannot add Dog to List<? extends Animal> List<? extends Animal> - you cannot call a method with a common parameter if the type of the reference variable has a wildcard of the upper bound. The type parameter of the map value can refer to any type that matches ? extends Collection<String> ? extends Collection<String> , possibly like HashMap<Integer, LinkedList<String>> . You can legitimately insert this line before calling put :

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

The compiler does not know the exact type that is actually on the map, so at compile time it should be in put ting a TreeSet<String> as the value for the map, the value of which could be something like LinkedList<String> .

In put value in map (except null ), you must remove the wildcard.

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

As JB Nizet commented, you can still put the value of any Collection , such as TreeSet , if you delete the template but save the Collection .

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

(In addition, a diamond operator can simplify declarations here.)

In response to the changes added to the question:

Here you used the lower bound.

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

The type parameter can be Collection<String> or any supertype, such as Object . This prohibits subtypes such as TreeSet<String> . Java generics are invariant. The only reason any variation from Collection<String> is allowed is related to the template.

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

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

+2
source

A map declaration tells the compiler that the value inside on the map is a collection of strings. At run time, this may be a TreeSet , but it may be another type of collection. Therefore, the compiler cannot resolve the placement of the TreeSet , because it can actually contain ArrayList values.

In general, whenever you use an argument of a bound type with a wildcard ? , you are practically allowed to read only from the map (for example, iterate over the elements). In other words, you can always:

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

In your case, however, since you are adding a TreeSet , that means you most likely 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 
+1
source

The compiler says:

No suitable method found for put (int, TreeSet) map.put (1, new TreeSet ()); HashMap.put (Integer, CAP # 1) method is not applicable (the actual TreeSet argument cannot be converted to CAP # 1 by converting the method call) AbstractMap.put (Integer, CAP # 1) method is not applicable (the actual TreeSet argument cannot be converted to CAP # 1 using method call conversion), where CAP # 1 is a new type variable: CAP # 1 extends the collection from capture? expands the collection

I think due to type ambiguity for the compiler and AR.3 said:

A map declaration tells the compiler that the value inside the map is some collection of strings. At run time, this may be a TreeSet, but it may be another type of collection.

If the type is defined for the compiler, the code will compile and run without any problems, for example, 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>())); //... //... } 
0
source

After reading the other answers and thinking a little more about the original question (which really concerned the existence of the “ ? extends ” part and did not have 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 usage methods elsewhere:

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

All of the above are compiled. Now ask what the doSomething method can do with the resulting collection? Can he add some number to it? The answer is no, it cannot. And this is because it will be unsafe; for example, if he added Integer (which is Number ), then the doSomething(longs) call failed. Thus, to prevent such a possibility, the compiler prohibits any attempt to add any Number at all to Collection<? extends Number> Collection<? extends Number> .

Similarly, if the method was declared as:

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

In this case, the method can add any quantity to the collection, but then calls like doSomething(integers) become compiler errors, since the Integer collection can only accept integers. Thus, the compiler further prevents the possibility for a ClassCastException , guaranteeing code security.

0
source

Source: https://habr.com/ru/post/1244757/


All Articles