List<List<? super String> doesn't work as expected - java

Assigning a List<Object> to a List<? super String> works fine.
Assigning a List<List<Object>> to a List<List<? super String>> doesn't compile.
Code
public class Main {
public static void main(String[] args) {
// works fine
List<Object> listOfObject = new ArrayList<>();
takeListSuperString(listOfObject);
// doesn't compile
List<List<String>> listOfListOfObject = new ArrayList<>();
takeListOfListSuperString(listOfListOfObject);
}
static void takeListSuperString(List<? super String> listSuperString) {
}
static void takeListOfListSuperString(List<List<? super String>> listOfListSuperString) {
}
}
Question
Why doesn't List<List<? super String>> work the same as List<? super String>?
Also, any idea where I could look up things like this?
A related question is Generics hell: hamcrest matcher as a method parameter. But I don't find the answers there helpful.
Edit
I had to think through JB Nizet's answer for a couple of hours before I finally got it. So I'll expand it a little bit here. Maybe that'll help someone else.
Assuming that assigning a List<List<CharSequence>> to a List<List<? super String>> is possible, the following code would compile:
// can only contain instances of CharSequence
List<List<CharSequence>> listOfListOfCharSequences = new ArrayList<>();
List<List<? super String>> listOfListSuperString = listOfListOfCharSequences;
// add a list of objects containing an Integer
List<Object> listOfObjects = new ArrayList<>();
listOfObjects.add(123);
listOfListSuperString.add(listOfObjects);
// Ups.. exception at runtime we are getting an Integer where we expect a CharSequence
CharSequence charSequence = listOfListOfCharSequences.get(0).get(0);
So to prevent ugly exceptions at runtime it is not allowed.
As halex points out this is Generics covariance, same as a List<String> not being assignable to List<Object>. And with using List<? extends List<? super String>> the code would actually compile, because ? extends String prevents the List.add() call.

Because a List<Object> is not the same thing as a List<? super String>. A List<? super String> holds objects of one specific type, that you don't know, but which is String or a super-class or super-interface of String. Whereas a List<Object> is a List that can hold any kind of object.
Suppose ? super String is CharSequence. Would you find it normal that the following compiles?
List<List<Object>> listOfListOfObjects = new ArrayList<List<Object>>();
List<Object> listOfObjects = new ArrayList<Object>();
listOfObjects.add(new Integer());
listOfListOfObjects.add(listOfObjects);
List<List<CharSequence>> listOfListOfCharSequences = listOfListOfObjects; // WTF?

You have to change the method takeListOfListSuperString to
static void takeListOfListSuperString(List<? extends List<? super String>> ListOfListSuperString)
The reason for this is covariance. The inner type List<? super String> must be matched exactly and that is not the case for List<String>. The workaround is to use another wildcard that tells Java that the combined inner type should also be covariant.

Okay question asked above is different... We can understand the question as follows:
List<? super String> a = new ArrayList<Object>();
List<List<? super String>> b = new ArrayList<List<Object>>();
List<List<? super String>> c = new ArrayList<List<? super String>>();
In this code line 2 is not compiling.... So why is not compiling?
Answer:
List<List<? super String>> means list of lists which are super class of String i.e. CharSequence or Object
But the object we are assigning says ArrayList of Lists which can accept any any class and every class.
So both the lists are different, and java shows this different at the time of compilations.

Related

Java ? super T in Collections copy why correct work

List<String> srcList = new ArrayList<>();
srcList.add("a");
srcList.add("b");
srcList.add("c");
List<String> descList = new ArrayList<>(3);
descList.add("1");
descList.add("2");
descList.add("3");
// public static <T> void copy(List<? super T> dest, List<? extends T> src)
Collections.copy(descList, srcList);
for (String item: descList) {
System.out.println(item);
}
List<? super String> listC = new ArrayList<>();
listC.add("A");
listC.add("B");
listC.add("C");
Collections.copy(listC, srcList);
for (String item : listC) { System.out.println(item); }// complie error.
<? super T> means T or T's super class.
In method Collections.copy() param dest can correct work on foreach.
Why custom List Object listC can't work on get item of this list.
By declaring List<? super String> listC, you're saying that listC could be a List<String>, or a List<Object> or a List<CharSequence>, or a List of any other supertype of String.
Now, the line for (String item: listC) makes no sense, because if listC is a List<Object>, or something else other than List<String>, then its contents may not necessarily be Strings. This is what the compiler is telling you.
This seems obvious that you have copied all elements from srcList to listC, so now the resultant list is of type List<? super String>.
when you're saying for (String item : listC) compiler will try to convert the type ? super String to String type. This is not allowed. Hence the compiler error. You should use the same type for both lists. As mentioned by #DawoodibnKareem, List<? super String> could be List<Object>, List<CharSequence> or a List of any other supertype of String. List of different types is not compatible.

Java generics list

I know following is true.
List<? extends Number> aNumberSuperList = new ArrayList<>();
List<? extends Integer> aIntegerSuperList = new ArrayList<>();
aNumberSuperList = aIntegerSuperList;
But what type of objects can be added to such a list.
List<? extends Number> aNumberSuperList2 = new ArrayList<>();
aNumberSuperList2.add(???)
Only null can be added to a List<? extends Number>. This is because the exact type parameter isn't known. A List<Integer> could be assigned to this list, and so could a List<AtomicLong>. The compiler must prevent a call to add with anything but null, to avoid the situation where a Integer might be added to something that's referred to as a List<? extends Number>, but is really a List<Double>, for example. null is the only safe thing to add here, because it can be of any type.

Java nested generic type

How come one must use the generic type Map<?, ? extends List<?>> instead of a simpler Map<?, List<?>> for the following test() method?
public static void main(String[] args) {
Map<Integer, List<String>> mappy =
new HashMap<Integer, List<String>>();
test(mappy);
}
public static void test(Map<?, ? extends List<?>> m) {}
// Doesn't compile
// public static void test(Map<?, List<?>> m) {}
Noting that the following works, and that the three methods have the same erased type anyways.
public static <E> void test(Map<?, List<E>> m) {}
Fundamentally, List<List<?>> and List<? extends List<?>> have distinct type arguments.
It's actually the case that one is a subtype of the other, but first let's learn more about what they mean individually.
Understanding semantic differences
Generally speaking, the wildcard ? represents some "missing information". It means "there was a type argument here once, but we don't know what it is anymore". And because we don't know what it is, restrictions are imposed on how we can use anything that refers to that particular type argument.
For the moment, let's simplify the example by using List instead of Map.
A List<List<?>> holds any kind of List with any type argument. So i.e.:
List<List<?>> theAnyList = new ArrayList<List<?>>();
// we can do this
theAnyList.add( new ArrayList<String>() );
theAnyList.add( new LinkedList<Integer>() );
List<?> typeInfoLost = theAnyList.get(0);
// but we are prevented from doing this
typeInfoLost.add( new Integer(1) );
We can put any List in theAnyList, but by doing so we have lost knowledge of their elements.
When we use ? extends, the List holds some specific subtype of List, but we don't know what it is anymore. So i.e.:
List<? extends List<Float>> theNotSureList =
new ArrayList<ArrayList<Float>>();
// we can still use its elements
// because we know they store Float
List<Float> aFloatList = theNotSureList.get(0);
aFloatList.add( new Float(1.0f) );
// but we are prevented from doing this
theNotSureList.add( new LinkedList<Float>() );
It's no longer safe to add anything to the theNotSureList, because we don't know the actual type of its elements. (Was it originally a List<LinkedList<Float>>? Or a List<Vector<Float>>? We don't know.)
We can put these together and have a List<? extends List<?>>. We don't know what type of List it has in it anymore, and we don't know the element type of those Lists either. So i.e.:
List<? extends List<?>> theReallyNotSureList;
// these are fine
theReallyNotSureList = theAnyList;
theReallyNotSureList = theNotSureList;
// but we are prevented from doing this
theReallyNotSureList.add( new Vector<Float>() );
// as well as this
theReallyNotSureList.get(0).add( "a String" );
We've lost information both about theReallyNotSureList, as well as the element type of the Lists inside it.
(But you may note that we can assign any kind of List holding Lists to it...)
So to break it down:
// ┌ applies to the "outer" List
// ▼
List<? extends List<?>>
// ▲
// └ applies to the "inner" List
The Map works the same way, it just has more type parameters:
// ┌ Map K argument
// │ ┌ Map V argument
// ▼ ▼
Map<?, ? extends List<?>>
// ▲
// └ List E argument
Why ? extends is necessary
You may know that "concrete" generic types have invariance, that is, List<Dog> is not a subtype of List<Animal> even if class Dog extends Animal. Instead, the wildcard is how we have covariance, that is, List<Dog> is a subtype of List<? extends Animal>.
// Dog is a subtype of Animal
class Animal {}
class Dog extends Animal {}
// List<Dog> is a subtype of List<? extends Animal>
List<? extends Animal> a = new ArrayList<Dog>();
// all parameterized Lists are subtypes of List<?>
List<?> b = a;
So applying these ideas to a nested List:
List<String> is a subtype of List<?> but List<List<String>> is not a subtype of List<List<?>>. As shown before, this prevents us from compromising type safety by adding wrong elements to the List.
List<List<String>> is a subtype of List<? extends List<?>>, because the bounded wildcard allows covariance. That is, ? extends allows the fact that List<String> is a subtype of List<?> to be considered.
List<? extends List<?>> is in fact a shared supertype:
List<? extends List<?>>
╱ ╲
List<List<?>> List<List<String>>
In review
Map<Integer, List<String>> accepts only List<String> as a value.
Map<?, List<?>> accepts any List as a value.
Map<Integer, List<String>> and Map<?, List<?>> are distinct types which have separate semantics.
One cannot be converted to the other, to prevent us from doing modifications in an unsafe way.
Map<?, ? extends List<?>> is a shared supertype which imposes safe restrictions:
Map<?, ? extends List<?>>
╱ ╲
Map<?, List<?>> Map<Integer, List<String>>
How the generic method works
By using a type parameter on the method, we can assert that List has some concrete type.
static <E> void test(Map<?, List<E>> m) {}
This particular declaration requires that all Lists in the Map have the same element type. We don't know what that type actually is, but we can use it in an abstract manner. This allows us to perform "blind" operations.
For example, this kind of declaration might be useful for some kind of accumulation:
static <E> List<E> test(Map<?, List<E>> m) {
List<E> result = new ArrayList<E>();
for(List<E> value : m.values()) {
result.addAll(value);
}
return result;
}
We can't call put on m because we don't know what its key type is anymore. However, we can manipulate its values because we understand they are all List with the same element type.
Just for kicks
Another option which the question does not discuss is to have both a bounded wildcard and a generic type for the List:
static <E> void test(Map<?, ? extends List<E>> m) {}
We would be able to call it with something like a Map<Integer, ArrayList<String>>. This is the most permissive declaration, if we only cared about the type of E.
We can also use bounds to nest type parameters:
static <K, E, L extends List<E>> void(Map<K, L> m) {
for(K key : m.keySet()) {
L list = m.get(key);
for(E element : list) {
// ...
}
}
}
This is both permissive about what we can pass to it, as well as permissive about how we can manipulate m and everything in it.
See also
"Java Generics: What is PECS?" for the difference between ? extends and ? super.
JLS 4.10.2. Subtyping among Class and Interface Types and JLS 4.5.1. Type Arguments of Parameterized Types for entry points to the technical details of this answer.
This is because the subclassing rules for generics are slightly different from what you may expect. In particular if you have:
class A{}
class B extends A{}
then
List<B> is not a subclass of List<A>
It's explained in details here and the usage of the wildcard (the "?" character) is explained here.

What is the difference between List<Something> and List<? extends Something>? [duplicate]

This question already has answers here:
When do Java generics require <? extends T> instead of <T> and is there any downside of switching?
(7 answers)
Closed 9 years ago.
Is there a difference between these two Lists?
List<Something> a;
List<? extends Something> b;
I know that for example
List<Object> a;
List<?> b;
List c;
and
List<Something> a;
List<? extends SomeInterfaceSomethingImplements> b;
are all different, but how about these? And is it different if Something was replaced with Object?
List<Object> a;
List<? extends Object> b;
List<Something> is a list of objects of type Something.
List<? extends Something> is a list of objects of some particular type which extends Something.
So, List<Object> can have objects of any class that extends Object.
But List<? extends Object> can only be initialised to or assigned as a List of a objects of a particular class that extends Object
When is this useful? Say you want to write a method that takes a List of Objects and prints each item:
void print(List<Object> list) {
for (Object obj: list) {
System.out.println(obj)
}
}
Now, let's say you have a List<Integer>. You cannot pass it to the above method, because print takes List<Object>, and List<Integer> cannot be assigned to a List<Object>. To get around this, we redefine print as:
void print2(List<? extends Object> list) {
for (Object obj: list) {
System.out.println(obj)
}
}
Now, we can pass List of any subclass of Object to print2. print2 will accept List<Integer>, List<String> etc.
On the flip side, you cannot add anything to the list inside print2, because print2 does not know the concrete subtype of Object which is used. Hence you can use ? extends ... only in methods where you do not have to add anything to the List.
As others have mentioned, List<Something> is a list of objects of type Something, whereas List<? extends Something> would be initialized to be a list of a different object type, as long as the object type extends Something.
List<Object> would be a list of objects of type Object.
List<? extends Object> could initialized to be a list of any data types that extend Object.
Here is some code showing what happens when using Object compared to ? extends Object:
import java.util.ArrayList;
public class test {
class A {}
public static void main(String[] args) {
// No error
ArrayList<Object> arrObj = new ArrayList<Object>();
// Error
ArrayList<Object> arrObj2 = new ArrayList<A>();
// No Error
ArrayList<? extends Object> arrExt = new ArrayList<Object>();
// No Error
ArrayList<? extends Object> arrExt2 = new ArrayList<A>();
}
}
List<Something> means the implementation should be something like ArrayList<Something>. But the other case List<? extends Something> means implementation can be anything like ArrayList<Something> or ArrayList<SomethingChild> where SomethingChild extends Something.
You can assign
List<? extends Something> list = new ArrayList<SomethingChild>();
whereas you cannot assign
List<Something> list = new ArrayList<SomethingChild>();
which will result in compilation error.
You can learn more about Generics and wildcards used in Generics for getting some perspicacity
First one is collection of object type something and the second one is collection of objects that are subtype of something.

Why does this simple Java generic function not compile?

While f1 does compile, the very similar f2 won't and I just cant explain why.
(Tested on Intellij 9 and Eclipse 3.6)
And really I thought I was done with that kind of question.
import java.util.*;
public class Demo {
public List<? extends Set<Integer>> f1(){
final List<HashSet<Integer>> list = null;
return list;
}
public List<List<? extends Set<Integer>>> f2(){
final List<List<HashSet<Integer>>> list = null;
return list;
}
}
List<List<HashSet<Integer>>> is not assignable to List<List<? extends Set<Integer>>> for the same reason List<HashSet<Integer>> would not be assignable to List<Set<Integer>>.
You can get it to compile by changing this:
public List<List<? extends Set<Integer>>> f2(){
into this:
public List<? extends List<? extends Set<Integer>>> f2(){
The reason your code didn't compile, and why the other example I gave (ie: "List<HashSet<Integer>> would not be assignable to List<Set<Integer>>") is that Java generics are not covariant.
The canonical example is that even if Circle extends Shape, List<Circle> does not extend List<Shape>. If it did, then List<Circle> would need to have an add(Shape) method that accepts Square objects, but obviously you don't want to be able to add Square objects to a List<Circle>.
When you use a wildcard, you're getting a type that slices away certain methods. List<? extends Shape> retains the methods that return E, but it doesn't have any of the methods that take E as a parameter. This means you still have the E get(int) method, but add(E) is gone. List<? extends Shape> is a super-type of List<Shape> as well as List<Circle>, List<? extends Circle>, etc. (? super wildcards slice the other way: methods that return values of the type parameter are removed)
Your example is more complicated because it has nested type parameters, but it boils down to the same thing:
List<HashSet<Integer>> is a sub-type of List<? extends Set<Integer>>
Because generics are not covariant, wrapping the two types in a generic type (like List<...>) yields a pair of types that no longer have the sub/super-type relationship. That is, List<List<HashSet<Integer>>> is not a sub-type of List<List<? extends Set<Integer>>>
If instead of wrapping with List<...> you wrap with List<? extends ...> you'll end up with the original relationship being preserved. (This is just a rule of thumb, but it probably covers 80% of the cases where you'd want to use wildcards.)
Note that trashgod and BalusC are both correct in that you probably don't want to be returning such a weird type. List<List<Set<Integer>>> would be a more normal return type to use. That should work fine as long as you're consistent about always using the collection interfaces rather than the concrete collection classes as type parameters. eg: you can't assign a List<ImmutableSet<Integer>> to a List<Set<Integer>>, but you can put ImmutableSet<Integer> instances into a List<Set<Integer>>, so never say List<ImmutableSet<Integer>>, say List<Set<Integer>>.
"Do not use wildcard types as return types. Rather than providing additional flexibility for your users, it would force them to use wildcard types in client code."—Joshua Bloch, Effective Java Second Edition, Chapter 5, Item 28.
This is too long to fit in a comment. I just wanted to say that it makes no sense to declare it that way. You can also just do the following:
public List<List<Set<Integer>>> f2() {
List<List<Set<Integer>>> list = new ArrayList<List<Set<Integer>>>();
List<Set<Integer>> nestedList = new ArrayList<Set<Integer>>();
list.add(nestedList);
Set<Integer> set = new HashSet<Integer>();
nestedList.add(set);
return list;
}
Works as good. I see no point of using ? extends SomeInterface here.
Update: as per the comments, you initially wanted to solve the following problem:
public List<Map<Integer, Set<Integer>>> getOutcomes() {
Map<HashSet<Integer>, Integer> map = new HashMap<HashSet<Integer>, Integer>();
List<Map<Integer, Set<Integer>>> outcomes = new ArrayList<Map<Integer, Set<Integer>>>();
for (Map.Entry<HashSet<Integer>, Integer> entry : map.entrySet()) {
outcomes.add(asMap(entry.getValue(), entry.getKey()));
// add() gives compiler error: The method add(Map<Integer,Set<Integer>>)
// in the type List<Map<Integer,Set<Integer>>> is not applicable for
// the arguments (Map<Integer,HashSet<Integer>>)
}
return outcomes;
}
public <K, V> Map<K, V> asMap(K k, V v) {
Map<K, V> result = new HashMap<K, V>();
result.put(k, v);
return result;
}
This can just be solved by declaring the interface (Set in this case) instead of implementation (HashSet in this case) as generic type. So:
public List<Map<Integer, Set<Integer>>> getOutcomes() {
Map<Set<Integer>, Integer> map = new HashMap<Set<Integer>, Integer>();
List<Map<Integer, Set<Integer>>> outcomes = new ArrayList<Map<Integer, Set<Integer>>>();
for (Map.Entry<Set<Integer>, Integer> entry : map.entrySet()) {
outcomes.add(asMap(entry.getValue(), entry.getKey()));
// add() now compiles fine.
}
return outcomes;
}
In future problems, try to ask how to solve a particular problem, not how to achieve a particular solution (which in turn may not be the right solution after all).

Categories

Resources