How to use addAll with generic collection? - java

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

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) {}

Java generic return generic list of type of parameter

I'd like to create the following:
//infer the type from parameter but restrict it to one of
// Proxy's subtype. return a list of this sub type
public static List<T> process(<T extends Proxy> proxy)
{
return new ArrayList<T>(); //just for example
}
Usage:
List<ConcreteTypeOfProxy> list = process(new ConcreteTypeOfProxy());
The above example has compilation issues. I think this should logically be available in java, just not sure what the syntax is
//Any array that extends T
public static <T extends Proxy> List<T> process(T proxy)
{
return new ArrayList<T>(); //just for example
}
//Returns array that has T type of parameter
public static <T> List<T> process(T proxy)
{
return new ArrayList<T>(); //just for example
}
//Returns a map of generic type which you define in the method
public static <T, E extends Proxy> Map<T, E> process(T key, E value)
{
Map<T, E> map = new HashMap<T, E>();
map.put(key, value);
return map;
}
You don't need any method, and consequently you don't need any parameters, to do this:
List<ConcreteTypeOfProxy> list = new ArrayList<>();
Remember: there is no difference between ArrayList<ConcreteTypeOfProxy> and ArrayList<AnyOtherType>: it's just an ArrayList.
The type parameter is merely an instruction to the compiler to check the type of what is added - at compile time only - and to automatically cast values obtained from the list.

Generic Types ("List<? extends> " ) in HashMap's Value

I did a generic type test.
class A {}
class B extends A {}
there are two methods:
private static void extendTest(List<? extends A> superList) {
System.out.println("extendTest over.");
}
private static void extendMapTest(HashMap<String, List<? extends A>> superMap) {
System.out.println("extendMapTest over.");
}
and I create a list and a map:
List<B> childList = new ArrayList<>();
childList.add(new B());
extendTest(childList); // TAG1: it is OK.
HashMap<String, List<B>> childMap = new HashMap<>();
childMap.put("hello", childList);
extendMapTest(childMap); // TAG2: ERROR!
why TAG1 is OK, and TAG2 is error?
how can I correct TAG2?
List<? extends A> is not a super type of List<B>. See the offical generics tutorial on upper bounding.
To fix, define childList like this:
List<A> childList = new ArrayList<>();
You can still add a B to a List<A>, because a
B is an A.
And define your map method like this:
private static void extendMapTest(HashMap<String, List<A>> superMap) {...
One thing to consider.
You can create another class
class C extends A {}
and inside your extendMapTest(HashMap<String, List<? extends A>> superMap)
you are free to do:
List<C> list = new ArrayList<>();
list.add(new C());
superMap.put("A", list);
but that is in contradiction with the declaration of the childMap object you're passing to extendMapTest
HashMap<String, List<B>> childMap
I guess the compilation error prevents this or similar situation
it is as simple like List<Integer> are not subtype of List<Number> even though Number and Integer are related and in the same way AnyClass<A> is not related with AnyClass<B> as shown in the docs too
The simple option is simply define your map with wild card too , no code change required ,like
HashMap<String, List<? extends A>> childMap = new HashMap<>();
e.g with modified code to demonstration
class A {}
class B extends A {
public int a=9;
}
private static void extendTest(List<? extends A> superList) {
System.out.println("extendTest over.");
}
private static void extendMapTest(HashMap<String, List<? extends A>> superMap) {
System.out.println("extendMapTest over."+((B)(superMap.get("hello").get(0))).a);
}
List<B> childList = new ArrayList<>();
childList.add(new B());
extendTest(childList);
HashMap<String, List<? extends A>> childMap = new HashMap<>();
childMap.put("hello", childList);
extendMapTest(childMap);
Output:
extendTest over.
extendMapTest over.9

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 get a type with multiple generic types to match the type parameters?

For example, say I want a Map<Class<?>, List<?>>, so I can put in a class and get out a list of that type - is there something I can replace the question marks with to make that happen?
You can do the trick if you delegate type check to the method:
private class TypedMap {
private Map<Class<?>, List<?>> map = new HashMap<>();
public <T> void put(Class<T> key, List<T> value) {
map.put(key, value);
}
#SupressWarnings("unchecked")
public <T> List<T> get(Class<T> clazz) {
return (List<T>) map.get(clazz);
}
}
Wildcard ? in map declaration does not ensure that key Class<?> and value List<?> would be of the same type. Method put() ensures that. You can not declare map as Map<Class<T>, List<T>> if your class is not generic - that's why you have to use a method.
Get method is unchecked. The cast is safe if entries are added with put() method. (There's still a problem with raw types - but this is unavoidable)
You can add more methods to TypedMap class, but remember about this restrictions.
public static void main(String[] args) {
TypedMap map = new TypedMap();
List<Cat> cats = new ArrayList<>();
List<Dog> dogs = new ArrayList<>();
adder.put(Cat.class, cats);
adder.put(Dog.class, dogs);
adder.put(Cat.class, dogs); // compilation error
}
Java doesn't completely enforce this, but one way to at least get a warning about it, is by using encapsulation:
public class MyClass {
// private, private, private
private Map<Class<?>, List<?>> myMap;
public <T> void put(Class<T> clazz, List<T> list) { // both must have the same T.
myMap.put(clazz, list);
}
...
}
You can still break this by doing something like:
MyClass mc = new MyClass();
Class c = Main.class;
List<String> l = new ArrayList<String>();
mc.put(c, l);
But you'll at least get a warning about unchecked conversion of c to Class<String>. And the unchecked invocation of MyClass::put
Not sure what you're trying to accomplish here, but Map<Class<T>, List<T>> would be the closest thing. The T is one single class type, though, so you can't put multiple classes into one Map.
You'll get a ClassCastException if you try to put objects of different classes into the same Map.

Categories

Resources