Method with ? extends Collections<String> is not applicable for Set<String> - java

Short, self contained, non-compilable example:
public void test1() throws Exception {
Map<String, Map<String, Set<String>>> a = Collections.singletonMap("One",
Collections.singletonMap("Two", new HashSet<String>()));
foo(a);
}
void foo(Map<String, Map<String, ? extends Collection<String>>> x) {
}
Yields (javac 1.8.0_102):
error: incompatible types: Map<String,Map<String,Set<String>>> cannot be converted to Map<String,Map<String,? extends Collection<String>>>
foo(a);
^
I expect foo to take any subtype of Collection like Set or List. What is wrong with code above?

The way you declared foo, the compiler demands a type parameter of the exact type Map<String, ? extends Collection<String>>. You could make foo generic:
<C extends Collection<String>> void foo(Map<String, Map<String, C>> x) {
}

Your code doesn't compile for the same reason the following (simpler) code doesn't compile:
List<Integer> l = Arrays.asList(1,2,3);
foo(l); //The method v(List<Object>) in the type Test is not applicable for the arguments (List<Integer>)
public void foo (List<Object> a){
...
}
The fact that Integer extends Object doesn't mean that List<Integer> extends List<Object>. They're two different, unrelated, types.
We can fix the compilation error in the simple case by changing foo's signature to:
public void foo (List<? extends Object> a){
...
}
This tells the compiler that foo will be accepting a List of something that extends Object, which is what List<Integer> is.
In your case, Map<String, Map<String, Set<String>>> doesn't extend Map<String, Map<String, ? extends Collection<String>>>.
Even if you changed your foo to void foo(Map<String, Object> x) it wouldn't compile since Map<String, Map<String, Set<String>>> does not extend Map<String, Object>. They're two different types.
To get it to work, you can change foo's signature to void foo(Map<String, ? extends Map<String, ? extends Collection<String>>> x)

This compiles
void foo(Map<String, ? extends Map<String, ? extends Collection<String>>> x)

Related

Generic Wildcards with Map

class SuperCl {}
class A extends SuperCl {}
class B extends SuperCl {}
static void method(Map<Integer, List<? extends SuperCl>> map) {}
public static void main(String[] args) {
method(new HashMap<Integer, List<A>>()); //ERROR
}
The compile time error is that the types are incompatible:
Map<Integer, List<A>> cannot be converted to Map<Integer, List<? extends SuperCl>>
How can I fix it and where does the error come from?
I assume it comes from the "method" being static.
EDIT: I changed the map implementation to HashMap (copy error) - this should not change anything
Change your method to
static <T extends SuperCl> void method(Map<Integer, List<T>> map) {
}
Edit: The error mainly comes from the use of a nested generic. If you would have something like
static void method (List<? extends SuperC1> list) {
}
public static void main (String[] args) {
List<A> list = new ArrayList<>();
method(list);
}
you would not get a compile time error because A satisfies ? extends SuperCl.
A HashMap<Integer, List<A>> isn't a Map<Integer, List<? extends SuperCl>>, because you can add any type of List<? extends SuperCl> to the latter.
For example:
Map<Integer, List<A>> original = new HashMap<Integer, List<A>>();
// Raw types to intentionally break the type system.
Map<Integer, List<? extends SuperCl>> map = (Map) original;
List<B> listOfB = new ArrayList<>();
listOfB.add(new B());
map.put(0, listOfB);
List<A> listOfA = original.values().iterator().next();
A item = listOfA.get(0); // ClassCastException.
If you could do that, you'd have been able to add a value that's not a List<A> to it. Hence it's not allowed.
You could change the type in the method signature to this, for example:
Map<Integer, ? extends List<? extends SuperCl>>
and that would be fine, because you can't put any value into that (other than literal null).
This works:
class SuperCl {}
class A extends SuperCl {}
class B extends SuperCl {}
static <T extends SuperCl> void method(Map<Integer, List<T>> map) {}
public static void main(String[] args) {
method(new HashMap<Integer, List<A>>()); //NO MORE ERROR
}
I simply moved the generics:
<T extends SuperCl>
to the static method declaration. This makes it verifiable at compile time. On the other hand, having that generic at the method argument is not compile time verifiable.
Generics are invariant. For parameterized types to be compatible, their type arguments must match exactly, unless one of them is a wildcard at the top level. Map<Integer, List<A>> is not a subtype of Map<Integer, List<? extends SuperCl>> because List<A> is not identical to List<? extends SuperCl>. Yes, List<A> is a subtype of List<? extends SuperCl>, but they are not identical, which is what is needed.
As you may know, List<Dog> is not a subtype of List<Animal>, even though Dog is a subtype of Animal. It's the same situation here. A subtype relationship of the type arguments does not lead to a subtype relationship of the parameterized types (that would be called "covariant"; Java array types are covariant, but generics are not).
One solution to this is to use a wildcard at the top level. For example, List<Dog> is a subtype of List<? extends Animal>. Similarly in your case, Map<Integer, List<A>> is a subtype of Map<Integer, ? extends List<? extends SuperCl>>. So you can declare your method as:
static void method(Map<Integer, ? extends List<? extends SuperCl>> map) {}

Inserting values in a typed map

Why would the code below result in the following error when trying to add to the Map?
Wrong 1st argument type. Found: 'com.test.Test.SomeEnums', required 'T'
public class Test {
public enum SomeEnums implements SomeType {
A;
public <T extends Enum<T> & SomeType> Map<T, Object> buildMap() {
Map<T, Object> map = new HashMap<>();
map.put(SomeEnums.A, new Object());
return map;
}
}
}
public interface SomeType {
}
Any ideas?
The problem is that map.put(SomeEnums.A, new Object()) is not always safe for Map<T, Object>. Although SomeEnums is a valid substitute for extends Enum<T> & SomeType, it is not always the concrete type parameter.
For example, consider this second enum:
enum OtherEnum implements SomeType {
B;
}
If the same method is to be called:
Map<OtherEnum, Object> otherMap = Test.SomeEnums.A.buildMap();
This is a valid call given the signature of buildMap(). However, the problem is that the method is adding an incorrect map key:
map.put(SomeEnums.A, new Object());
//SomeEnums.A is not always of type <T>, so this is not allowed.
The code will compile with a type cast (map.put((T) SomeEnums.A, new Object())) - with a warning, but that's unsafe and likely not the point of the generic method.

Return value of multi-level generic type cannot be assigned to an extended type

I have this convenient method (which I have been using for many years without problems). It just converts a List to a Map<SomeKey, List>, grouping them by a key attribute.
To avoid unnecessary casting, I'm passing the key attribute as a String (which refers to a method name) and I'm also specifying the type of that attribute.
#SuppressWarnings({"unchecked"})
#Nullable
public static <K, E> Map<K, List<E>> getMultiMapFromList(Collection<E> objectList, String keyAttribute, Class<K> contentClass)
{
// creates a map from a list of objects using reflection
...
}
The above method has been working flawlessly for many years in many applications. But today the following case raises a problem:
List<? extends MyBean> fullBeanList = getFullBeanList();
Map<MyKey, List<? extends MyBean>> multiMap;
// the following line doesn't compile.
multiMap = Utils.getMultiMapFromList(fullBeanList, "key", MyKey.class);
During development there are no warnings what so ever from my IntelliJ IDE.
But during compilation this appears:
Error:(...,...) java: incompatible types: java.util.Map<mypackage.MyKey, java.util.List<capture #2 of ? extends mypackage.MyBean>> cannot be converted to java.util.Map<mypackage.MyKey, java.util.List<? extends mypackage.MyBean>>
I can't figure this one out though.
My guess it has something to do with the ? extends. But I don't see any violations. And I'm also wondering a bit about why it only appears at compilation time? I would think that due to type erasure it doesn't even matter once it's compiled anyway.
I'm sure I could force this by adding some casts, but I would like to understand what's happening here.
EDIT:
for convenience:
Test.java
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
public class Test
{
public static void main(String[] args)
{
List<? extends MyBean> input = new ArrayList<>();
Map<MyKey, List<? extends MyBean>> output;
output = test(input, MyKey.class); // doesn't compile
}
public static <K, E> Map<K, List<E>> test(Collection<E> a, Class<K> b)
{
return null;
}
private static class MyKey{}
private static class MyBean{}
}
EDIT 2
To continue one step further in the madness:
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
public class Test
{
public static void main(String[] args)
{
List<? extends Number> input = new ArrayList<>();
// compiles fine
List<? extends Number> output1 = test1(input);
// doesn't compile
Map<String, List<? extends Number>> output2 = test2(input);
}
public static <E> List<E> test1(Collection<E> a) { return null;}
public static <E, K> Map<K, List<E>> test2(Collection<E> a) { return null;}
}
I'm not sure what to think of this. As long as I use 1 level of generics then it works fine. But when I use 2-level generics (i.e. generics in generics, e.g. Map<K,List<V>>) then it fails.
This will resolve your problem.
You have to change the method test as like below.
public static <K, E> Map<K, List<? extends E>> test(
Collection<? extends E> a, Class<K> b) {
return null;
}
The problem is that you are not telling ?s passed to the method and in Java they aren't guaranteed to be the same. Make this method generic, so that you have a generic type parameter to reference and to be the same throughout the method.
Below is the code.
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
public class Test {
public static void main(String[] args) {
List<? extends MyBean> input = new ArrayList<>();
Map<MyKey, List<? extends MyBean>> output;
output = test(input, MyKey.class); // doesn't compile
}
public static <K, E> Map<K, List<? extends E>> test(
Collection<? extends E> a, Class<K> b) {
return null;
}
private static class MyKey {
}
private static class MyBean {
}
}
After reading Dilip Singh Kasana's answer I still didn't get it.
But then I came accross this article, which explained it to me.
I am not going to copy the whole thing, but just the part that enlighted me.
Collection< Pair<String,Long> > c1 = new ArrayList<Pair<String,Long>>();
Collection< Pair<String,Long> > c2 = c1; // fine
Collection< Pair<String,?> > c3 = c1; // error
Collection< ? extends Pair<String,?> > c4 = c1; // fine
Of course, we can assign a Collection<Pair<String,Long>> to a
Collection<Pair<String,Long>>. There is nothing surprising here.
But we can not assign a Collection<Pair<String,Long>> to a
Collection<Pair<String,?>>. The parameterized type
Collection<Pair<String,Long>> is a homogenous collection of pairs of
a String and a Long ; the parameterized type
Collection<Pair<String,?>> is a heterogenous collection of pairs of a
String and -something of unknown type-. The heterogenous
Collection<Pair<String,?>> could for instance contain a
Pair<String,Date> and that clearly does not belong into a
Collection<Pair<String,Long>>.
For this reason the assignment is not
permitted.
Applying it to the question.
If we supply an input of type List<? extends Number> to a method
public static <E> Map<String, List<E>> test(Collection<E> objectList) then it will actually return a Map<String, List<? extends Number>.
But this return-value cannot be assigned to a field of the exact same type Map<String, List<? extends Number>.
The reason for this, is that the returned map could be a Map<String, List<Integer>. If I were to assign it to a Map<String, List<? extends Number>, then I could later on put a List<Double> in it. That would clearly break it, but nothing would stop me from doing it.
Consider this:
// behind the scenes there's a map containing Integers.
private static Map<String, List<Integer>> myIntegerMap = new HashMap<>;
// both collections return the same thing, but one of them hides the exact type.
public static Map<String, List<? extends Number> getMap() { return myIntegerMap; }
public static Map<String, List<Integer>> getIntegerMap() { return myIntegerMap; }
private static void test()
{
// fortunately the following line does not compile
Map<String, List<? extends Number> map = getMap();
// because nothing would stop us from adding other types.
List<Double> myDoubleList = new ArrayList<>();
myDoubleList.add(Double.valueOf(666));
map.put("key", myDoubleList);
// if it would compile, then this list would contain a list with doubles.
Map<String, List<Integer>> brokenMap = getIntegerMap();
}
As Dilip Singh Kasana pointed out, it does work if the method would return a Map<String, List<? extends Number>>. Adding the extends changes everything.
// still the same map.
private static Map<String, List<Integer>> myIntegerMap = new HashMap<>;
// the return value is an extended type now.
public static Map<String, ? extends List<? extends Number> getMap() { return myIntegerMap; }
public static Map<String, List<Integer>> getIntegerMap() { return myIntegerMap; }
private static void test()
{
// the following compiles now.
Map<String, ? extends List<? extends Number> map = getMap();
// if we try to add something now ...
List<Double> myDoubleList = new ArrayList<>();
myDoubleList.add(Double.valueOf(666));
// the following won't compile.
map.put("key", myDoubleList);
}
So, this time assigning it works, but the resulting type is a "read-only" map. (PS: For the sake of being complete. Stating the obvious: You can't add anything to a collection or map with an ? extends X type. Those collections are "read-only", that makes perfect sense.)
So the compile time error prevents this situation where the map could be broken.

How to use addAll with generic collection?

Why does
List<Map<String, Object>> a, b;
a.addAll(b);
compile
But
List<? extends Map<String, ?>> a, b;
a.addAll(b);
does not.
How to make the latter compile?
Imagine that CustomHashMap extends HashMap and you initialize a like following:
List<CustomHashMap<String, String> list = new ArrayList<CustomHashMap<String, String>>();
List<? extends Map<String, ?>> a = list;
if you were able to to add entries to a...
a.add(new HashMap<String, String>());
...you would encounter this strange situation
CustomHashMap<String, String> map = list.get(0); // you would expect to get CustomHashMap object but you will get a HashMap object instead
In other words, you don't know the actual type of you Map (when you say ? extends Map), everything you know is that it is a some subtype of Map and you can not add arbitrary objects to the List because you need to ensure that there is a supertype for the added object. But you can't since the exact type of the Map is unknown.
You cannot add items to a wildcard generic list. The first one compiles because it has a defined type.
You may, however, find some super class that suits your purpose and use it as generic argument.
UPDATE:
see also Wildcard (in the Oracle docs)
According java docs you can declare helper method in this case (ugly but works):
static <T extends List<K>, V, K extends Map<String, V>> void addAllHelper(T from, T to) {
from.addAll(to);
}
When you declare it with concrete Generic Type, it compiles. If you define a class:
public class AClass<T extends Map<String, ?>> {
public List<T> dostuff() {
List<T> lista = new ArrayList<T>();
List<T> listb = new ArrayList<T>();
lista.addAll(listb);
return lista;
}
}
With wildcards it cannot compile.
You can do it using a method like:
private <S extends Map<String, ?>> void method(List<S> a, List<S> b){
a.addAll(b);
}
Below code should help you explain this
List<? super Number> a is called out parameter and can be used for updates
List<? extends Number> b is called in parameter and can be used to read
Commented lines below results in compilation error for the reason as mentioned in comments
public static void main(String[] args) {
List<Integer> intList = new ArrayList<>();
List<Double> doubleList = new ArrayList<>();
List<Number> numberList = new ArrayList<>();
addAll(numberList, intList);
addAll(numberList, doubleList);
addAll(intList, doubleList);//cant add double to int
addAll(doubleList,intList);//cant add int double
addAll(intList, numberList);//cant add number to int
}
private static<T> void addAll(List<? super T> a, List<? extends T> b) {
a.addAll(b);
}

Java -- passing generic parameters

I am trying to write a method that takes in any subclass of MyInterface as a parameter, but getting syntax errors.
public Map<String, List<T extends MyInterface>> makeMap(List<T extends MyInterface>) {
Map<String, List<T extends MyInterface>> myMap = ...
return myMap;
}
This syntax is not valid. The signature gives the error "misplaced construct". But, the idea is that I can pass any subclass of MyInterface inn place of T. Can this be done in Java? how?
You are mixing up the concepts of declaring a generic type and referring to that generic type. Assuming that you want the method to be generic, declare the generic type parameter before the return type, then refer to it plainly as T elsewhere:
// Declaration ref ref
public <T extends MyInterface> Map<String, List<T>> makeMap(List<T>) {
// ref
Map<String, List<T>> myMap = ...
return myMap;
}
public <T extends MyInterface> Map<String, List<T>> makeMap(List<T> myList) {
More on generic methods here
I also noticed that in your original method declaration, you didn't have a variable defined for your method parameter.

Categories

Resources