I'm wondering why this code couldn't be compiled:
private static List<? super String> x() {
return null;
}
List<Object> l = x();
List is of type object so we can store everything in it. Who can explain?
List<Object> != List<? super String>
You can store everything on it.
l.add("a");
l.add(1);
l.add(new Object());
But you cannot assign a List<? super String> to a List<Object> because they aren't the same thing.
You can add a BigDecimal to a List<Object>, but you cannot add a BigDecimal to a List<? super String>.
Related
Given these lines of code, how are they type erased?
I understand that for this one, it goes as follows (correct me if i'm wrong please!):
List<String> l = new ArrayList<>();
// type erased to:
List l = new ArrayList();
But what about for these two?
List<? extends Integer> l = new ArrayList<>();
// Does it get erased to:
List<Integer> l = new ArrayList<>(); ?
List<Object> l = new ArrayList<>(); ?
List l = new ArrayList(); ?
// Assume some generic class
List<? extends T> l = new ArrayList<>();
// Does it get erased to:
List<T> l = new ArrayList<>(); ?
List<Object> l = new ArrayList<>(); ?
List l = new ArrayList(); ?
If ? extends Integer gets erased to Integer, why doesn't it get erased fully to Object, since List<Integer> gets erased to List? Or am I confused between List<Object> and List?
PS, confused with the whole type erasure with generics thing.
If you have a parameterised type (in simple terms, a type with <>s at the end), it always erases to the non-parameterised type, so List<Integer>, List<T>, List<? extends Integer>, List<? extends T> all erases to List. It doesn't matter what you put in the <>.
You might have confused this with how type parameters erase. If you have a type parameter, such as the T here:
class Foo<T extends Integer> {
private T bar;
}
bar's type erases to Integer:
// after erasure
class Foo {
private Integer bar;
}
Type parameters erase to the erasure of their (first) constraint. T extends Integer isn't a particularly useful constraint, so here's another example:
class Foo<T extends Comparable<T>> {
private T bar;
}
erases to:
class Foo {
private Comparable bar;
}
T erases to the erasure of Comparable<T>. Comparable<T> erases to Comparable, so the type of bar is Comparable.
Further reading: Java Language Specification section 4.6
This compiles (1.6)
List<? extends Object> l = new ArrayList<Date>();
But this does not
List<List<? extends Object>> ll = new ArrayList<List<Date>>();
with the error of
Type mismatch: cannot convert from ArrayList<List<Date>> to List<List<? extends Object>>
Could someone explain why?
Thanks
EDIT: edited for being consequent
Well the explanations are correct, but I think it'd be a nice thing to add the actual working solution as well ;)
List<? extends List<? extends Object>>
Will work just fine, but obviously the use of such a collection is quite limited by the usual limitations of generic Collections (but then the same is true for the simpler List< ? extends Date >)
Because it would break type safety:
List<List<Object>> lo = new ArrayList<List<Object>>();
List<List<? extends Object>> ll = lo;
List<String> ls = new ArrayList<String>();
ll.add(ls);
lo.get(0).add(new Object());
String s = ls.get(0); // assigns a plain Object instance to a String reference
Suppose D is subtype of B, G<T> is a generic type
B x = new D(); // OK
G<B> y = new G<D>(); // FAIL
Now, G<Date> is a subtype of G<?>, therefore
G<?> x = new G<Date>(); // OK
G<G<?>> y = new G<G<Date>>(); // FAIL
When assigning to a variable (List<T>) with a non-wildcard generic type T, the object being assigned must have exactly T as its generic type (including all generic type parameters of T, wildcard and non-wildcard). In your case T is List<? extends Object>, which is not the same type as List<Date>.
What you can do, because List<Date> is assignable to List<? extends Object>, is use the wildcard type:
List<? extends List<? extends Object>> a = new ArrayList<List<Date>>();
<? extends Object>
means that the wildcard could be substituted only for those objects, that are subclass of Object class.
List<List<? extends Object>> ll = new ArrayList<List<Object>>();
gives you error of type mismatch because you are trying to assign a ArrayList of List of object of java class Object to a List that contains the List of any type of objects that are subclass of java class Object.
For more ref, have a look at the Wildcard documentation
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.
Sorry for the yet another "Java generic method is not applicable" question. I would like to know what am I missing in my understanding:
List<E> is a subtype of Collection<E>
--> meaning, List<String> is a subtype of Collection<String>
Suppose A extends B, List<A> is not a subtype of List<B>
--> but in this case, there's only one type T (or String), so I don't see how the Substitution Principle can explain my problem?
Problem Code:
private <T, K> void genericAddToMapOfLists(HashMap<K, Collection<T>> mapOfLists,
K key, T value) {
if (mapOfLists.containsKey(key)) {
mapOfLists.get(key).add(value);
} else {
List<T> newList = new ArrayList<T>();
newList.add(value);
mapOfLists.put(key, newList);
}
}
private void parseToFruitList(HashMap<String, List<String>> fruit_colors,
String fruitName) {
String color = "";
genericAddToMapOfLists(fruit_colors, fruitName, color);
}
Error:
The method genericAddToMapOfLists(HashMap<K,Collection<T>>, K, T) in the type MyGroceryStore is not applicable for the arguments (HashMap<String,List<String>>, String, String)
The code works when I change the method signature to genericAddToMapOfLists(HashMap<K,List<T>>, K, T).
This is exactly the problem you are explaining in your second point.
Suppose A extends B, List<A> is not a subtype of List<B>
In this case your method expects
HashMap<?, Collection<?>>
but you are giving it
HashMap<?, List<?>>
List extends Collection, but HashMap<?, List> is not a subtype of HashMap<?, Collection>
(I'm not using ? as a wildcard, we just don't care about it right now)
You're right in that "List<String> is a subtype of Collection<String>". And if A extends B, List<A> is not a subtype of List<B>.
Taking that one step further, a HashMap<String, List<String>> is not a HashMap<String, Collection<String>>.
The same reasoning applies, where A is List and B is Collection. If a HashMap<String, List<String>> was a HashMap<String, Collection<String>>, then you could put a Vector<String> into a HashMap<String, List<String>> by assigning it to a HashMap<String, Collection<String>>, even though a Vector isn't a List, and so it's not allowed.
This compiles (1.6)
List<? extends Object> l = new ArrayList<Date>();
But this does not
List<List<? extends Object>> ll = new ArrayList<List<Date>>();
with the error of
Type mismatch: cannot convert from ArrayList<List<Date>> to List<List<? extends Object>>
Could someone explain why?
Thanks
EDIT: edited for being consequent
Well the explanations are correct, but I think it'd be a nice thing to add the actual working solution as well ;)
List<? extends List<? extends Object>>
Will work just fine, but obviously the use of such a collection is quite limited by the usual limitations of generic Collections (but then the same is true for the simpler List< ? extends Date >)
Because it would break type safety:
List<List<Object>> lo = new ArrayList<List<Object>>();
List<List<? extends Object>> ll = lo;
List<String> ls = new ArrayList<String>();
ll.add(ls);
lo.get(0).add(new Object());
String s = ls.get(0); // assigns a plain Object instance to a String reference
Suppose D is subtype of B, G<T> is a generic type
B x = new D(); // OK
G<B> y = new G<D>(); // FAIL
Now, G<Date> is a subtype of G<?>, therefore
G<?> x = new G<Date>(); // OK
G<G<?>> y = new G<G<Date>>(); // FAIL
When assigning to a variable (List<T>) with a non-wildcard generic type T, the object being assigned must have exactly T as its generic type (including all generic type parameters of T, wildcard and non-wildcard). In your case T is List<? extends Object>, which is not the same type as List<Date>.
What you can do, because List<Date> is assignable to List<? extends Object>, is use the wildcard type:
List<? extends List<? extends Object>> a = new ArrayList<List<Date>>();
<? extends Object>
means that the wildcard could be substituted only for those objects, that are subclass of Object class.
List<List<? extends Object>> ll = new ArrayList<List<Object>>();
gives you error of type mismatch because you are trying to assign a ArrayList of List of object of java class Object to a List that contains the List of any type of objects that are subclass of java class Object.
For more ref, have a look at the Wildcard documentation