In this example:
import java.util.*;
public class Example {
static void doesntCompile(Map<Integer, List<? extends Number>> map) {}
static <T extends Number> void compiles(Map<Integer, List<T>> map) {}
static void function(List<? extends Number> outer)
{
doesntCompile(new HashMap<Integer, List<Integer>>());
compiles(new HashMap<Integer, List<Integer>>());
}
}
doesntCompile() fails to compile with:
Example.java:9: error: incompatible types: HashMap<Integer,List<Integer>> cannot be converted to Map<Integer,List<? extends Number>>
doesntCompile(new HashMap<Integer, List<Integer>>());
^
while compiles() is accepted by the compiler.
This answer explains that the only difference is that unlike <? ...>, <T ...> lets you reference the type later, which doesn't seem to be the case.
What is the difference between <? extends Number> and <T extends Number> in this case and why doesn't the first compile?
By defining the method with the following signature:
static <T extends Number> void compiles(Map<Integer, List<T>> map) {}
and invoking it like:
compiles(new HashMap<Integer, List<Integer>>());
you're matching T against the type you're providing.
In the jls §8.1.2 we find, that (interesting part bolded by me):
A generic class declaration defines a set of parameterized types (§4.5), one for each possible invocation of the type parameter section by type arguments. All of these parameterized types share the same class at run time.
In other words, the type T is matched against the input type and assigned Integer. The signature will effectively become static void compiles(Map<Integer, List<Integer>> map).
When it comes to doesntCompile method, jls defines rules of subtyping (§4.5.1, bolded by me):
A type argument T1 is said to contain another type argument T2, written T2 <= T1, if the set of types denoted by T2 is provably a subset of the set of types denoted by T1 under the reflexive and transitive closure of the following rules (where <: denotes subtyping (§4.10)):
? extends T <= ? extends S if T <: S
? extends T <= ?
? super T <= ? super S if S <: T
? super T <= ?
? super T <= ? extends Object
T <= T
T <= ? extends T
T <= ? super T
This means, that ? extends Number indeed contains Integer or even List<? extends Number> contains List<Integer>, but it's not the case for Map<Integer, List<? extends Number>> and Map<Integer, List<Integer>>. More on that topic can be found in this SO thread. You can still make the version with ? wildcard work by declaring, that you expect a subtype of List<? extends Number>:
public class Example {
// now it compiles
static void doesntCompile(Map<Integer, ? extends List<? extends Number>> map) {}
static <T extends Number> void compiles(Map<Integer, List<T>> map) {}
public static void main(String[] args) {
doesntCompile(new HashMap<Integer, List<Integer>>());
compiles(new HashMap<Integer, List<Integer>>());
}
}
In the call:
compiles(new HashMap<Integer, List<Integer>>());
T is matched to Integer, so the type of the argument is a Map<Integer,List<Integer>>. It's not the case for the method doesntCompile: the type of the argument stays Map<Integer, List<? extends Number>> whatever the actual argument in the call; and that is not assignable from HashMap<Integer, List<Integer>>.
UPDATE
In the doesntCompile method, nothing prevents you to do something like this:
static void doesntCompile(Map<Integer, List<? extends Number>> map) {
map.put(1, new ArrayList<Double>());
}
So obviously, it cannot accept a HashMap<Integer, List<Integer>> as the argument.
Simplied example of demonstration. Same example can be visualize like below.
static void demo(List<Pair<? extends Number>> lst) {} // doesn't work
static void demo(List<? extends Pair<? extends Number>> lst) {} // works
demo(new ArrayList<Pair<Integer>()); // works
demo(new ArrayList<SubPair<Integer>()); // works for subtype too
public static class Pair<T> {}
public static class SubPair<T> extends Pair<T> {}
List<Pair<? extends Number>> is a multi-level wildcards type whereas List<? extends Number> is a standard wildcard type .
Valid concrete instantiations of the wild card type List<? extends Number> include Number and any subtypes of Number whereas in case of List<Pair<? extends Number>> which is a type argument of type argument and itself has a concrete instantiation of the generic type.
Generics are invariant so Pair<? extends Number> wild card type can only accept Pair<? extends Number>>. Inner type ? extends Number is already covariant. You have to make the enclosing type as covariant to allow covariance.
I'd recommend you to look in documentation of generic wildcards especially guidelines for wildcard use
Frankly speaking your method #doesntCompile
static void doesntCompile(Map<Integer, List<? extends Number>> map) {}
and call like
doesntCompile(new HashMap<Integer, List<Integer>>());
Is fundamentally incorrect
Let's add legal implementation:
static void doesntCompile(Map<Integer, List<? extends Number>> map) {
List<Double> list = new ArrayList<>();
list.add(0.);
map.put(0, list);
}
It is really fine, because Double extends Number, so put List<Double> is absolutely fine as well as List<Integer>, right?
However, do you still suppose it's legal to pass here new HashMap<Integer, List<Integer>>() from your example?
Compiler does not think so, and is doing his (its?) best to avoid such situations.
Try to do the same implementation with method #compile and compiler will obviously does not allow you to put a list of doubles into map.
static <T extends Number> void compiles(Map<Integer, List<T>> map) {
List<Double> list = new ArrayList<>();
list.add(10.);
map.put(10, list); // does not compile
}
Basically you can put nothing but List<T> that's why it's safe to call that method with new HashMap<Integer, List<Integer>>() or new HashMap<Integer, List<Double>>() or new HashMap<Integer, List<Long>>() or new HashMap<Integer, List<Number>>().
So in a nutshell, you are trying to cheat with compiler and it fairly defends against such cheating.
NB: answer posted by Maurice Perry is absolutely correct. I'm just not sure it's clear enough, so tried (really hope I managed to) to add more extensive post.
Related
I have a couple of questions about generic wildcards in Java:
What is the difference between List<? extends T> and List<? super T>?
What is a bounded wildcard and what is an unbounded wildcard?
In your first question, <? extends T> and <? super T> are examples of bounded wildcards. An unbounded wildcard looks like <?>, and basically means <? extends Object>. It loosely means the generic can be any type. A bounded wildcard (<? extends T> or <? super T>) places a restriction on the type by saying that it either has to extend a specific type (<? extends T> is known as an upper bound), or has to be an ancestor of a specific type (<? super T> is known as a lower bound).
The Java Tutorials have some pretty good explanations of generics in the articles Wildcards and More Fun with Wildcards.
If you have a class hierarchy A, B is a subclass of A, and C and D are both subclasses of B like below
class A {}
class B extends A {}
class C extends B {}
class D extends B {}
Then
List<? extends A> la;
la = new ArrayList<B>();
la = new ArrayList<C>();
la = new ArrayList<D>();
List<? super B> lb;
lb = new ArrayList<A>(); //fine
lb = new ArrayList<C>(); //will not compile
public void someMethod(List<? extends B> lb) {
B b = lb.get(0); // is fine
lb.add(new C()); //will not compile as we do not know the type of the list, only that it is bounded above by B
}
public void otherMethod(List<? super B> lb) {
B b = lb.get(0); // will not compile as we do not know whether the list is of type B, it may be a List<A> and only contain instances of A
lb.add(new B()); // is fine, as we know that it will be a super type of A
}
A bounded wildcard is like ? extends B where B is some type. That is, the type is unknown but a "bound" can be placed on it. In this case, it is bounded by some class, which is a subclass of B.
Josh Bloch also has a good explanation of when to use super and extends in this google io video talk where he mentions the Producer extends Consumer super mnemonic.
From the presentation slides:
Suppose you want to add bulk methods to Stack<E>
void pushAll(Collection<? extends E> src);
– src is an E producer
void popAll(Collection<? super E> dst);
– dst is an E consumer
There may be times when you'll want to restrict the kinds of types that are allowed to be passed to a type parameter. For example, a method that operates on numbers might only want to accept instances of Number or its subclasses. This is what bounded type parameters are for.
Collection<? extends MyObject>
means that it can accept all object who have IS- A relationship with MyObject (i.e. any object which is a type of myObject or we can say any object of any subclass of MyObject) or a object of MyObject class.
For example:
class MyObject {}
class YourObject extends MyObject{}
class OurObject extends MyObject{}
Then,
Collection<? extends MyObject> myObject;
will accept only MyObject or children of MyObject(i.e. any object of type OurObject or YourObject or MyObject, but not any object of superclass of MyObject).
In general,
If a structure contains elements with a type of the form ? extends E, we can get elements out of the structure, but we cannot put
elements into the structure
List<Integer> ints = new ArrayList<Integer>();
ints.add(1);
ints.add(2);
List<? extends Number> nums = ints;
nums.add(3.14); // compile-time error
assert ints.toString().equals("[1, 2, 3.14]");
To put elements into the structure we need another kind of wildcard called Wildcards with super,
List<Object> objs = Arrays.<Object>asList(2, 3.14, "four");
List<Integer> ints = Arrays.asList(5, 6);
Collections.copy(objs, ints);
assert objs.toString().equals("[5, 6, four]");
public static <T> void copy(List<? super T> dst, List<? extends T> src) {
for (int i = 0; i < src.size(); i++) {
dst.set(i, src.get(i));
}
}
Generic wildcards are created to make methods that operate on Collection more reusable.
For example, if a method has a parameter List<A>, we can only give List<A> to this method. It is a waste for this method's funtion under some circumstances:
If this method only reads objects from List<A>, then we should be allowed to give List<A-sub> to this method. (Because A-sub IS a A)
If this method only inserts objects to List<A>, then we should be allowed to give List<A-super> to this method. (Because A IS a A-super)
learn by example:
consider the sort() method in Collections class which use both extends and super:
public static <T extends Comparable<? super T>> void sort(List<T> list){...}
so
why <T extends Comparable<...>>: becuase we need list items (T) to be a subclass of the Comparable interface.
why Comparable<? super T>: becuase we allow the Comparable type
to be a Comparable of any super type of T.
Consider
interface Comparable<T>{
public int compareTo(T o);
}
public static <T extends Comparable<? super T>> void sort(List<T> list){...}
public static <T extends Comparable<T>> void sort2(List<T> list){...}
class A implements Comparable<A>{
#Override
public int compareTo(A o) {
...
}
}
class B extends A {
}
List<A> listA = new ArrayList<>();
List<B> listB = new ArrayList<>();
sort(listA); //ok
sort(listB); //ok
sort2(listA); //ok
sort2(listB); //Error
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) {}
I have TypeToken class used to represent some generic type like this:
TypeToken<List<String>> listOfStrings = new TypeToken<List<String>> {}
And this works fine, TypeToken is just class TypeToken<T> {} with simple method to get that type.
Now I wanted to create simple methods for common type like List for more dynamic usage:
TypeToken<List<? extends Number>> numbers = list(extendsType(Number.class))
using:
public static <T> TypeToken<? extends T> extendsType(Class<T> type) {return null;}
public static <T> TypeToken<List<T>> list(TypeToken<T> type) {return null;}
(return nulls as I'm only asking about compiler not logic)
But for some reason this does not work how I would expect: (as code that I expected to be valid does not compile, and code that I expected to be invalid does compile)
class TypeToken<X> {
static <T> TypeToken<? extends T> extendsType(Class<T> type) {return null;}
static <T> TypeToken<List<T>> list(TypeToken<T> type) {return null;}
static void wat() {
TypeToken<List<? extends Number>> a = new TypeToken<List<? extends Number>>() {}; // valid
TypeToken<List<? extends Number>> b = list(extendsType(Number.class)); // invalid, why?
TypeToken<? extends List<? extends Number>> c = list(extendsType(Number.class)); // valid, why?
}
}
What I'm doing wrong here? And what is causing generics to behave like this?
I'm using JDK 11, but I also tested this on JDK 8
Compiler error:
error: incompatible types: no instance(s) of type variable(s) T#1,CAP#1,T#2 exist so that TypeToken<List<T#1>> conforms to TypeToken<List<? extends Number>>
TypeToken<List<? extends Number>> b = list(extendsType(Number.class)); // invalid, why?
^
where T#1,T#2 are type-variables:
T#1 extends Object declared in method <T#1>list(TypeToken<T#1>)
T#2 extends Object declared in method <T#2>extendsType(Class<T#2>)
where CAP#1 is a fresh type-variable:
CAP#1 extends T#2 from capture of ? extends T#2
I think that at the core this question that has already been asked in a similar form several times. But I'm not sure, because this is one of the constellations where it is particularly hard to wrap one's head around the concept.
Maybe one can imagine it like that:
The extendsType method here returns a TypeToken<? extends Number>
In the call to the list method, the ? is captured in the type parameter T. This capture can (roughly speaking) be imagined as a new type variable - something like X extends Number, and the method returns a TypeToken<List<X>>
Now the TypeToken<List<X>> is not assignable to TypeToken<List<? extends Number>>, as of the usual constraints. Maybe this table, with <=== indicating assignability and <=/= indicating non-assignability, will help:
Number <=== Integer
TypeToken< Number> <=/= TypeToken<Integer>
TypeToken<? extends Number> <=== TypeToken<Integer>
List<? extends Number> <=== List<X>
TypeToken< List<? extends Number>> <=/= TypeToken<List<X>>
TypeToken<? extends List<? extends Number>> <=== TypeToken<List<X>>
So in the end, the answer to the question which super-subtype relationships exist among instantiations of generic types in the famous Generics FAQ by Angelika Langer is probably once more the most relevant here:
Super-subtype relationships among instantiations of generic types are determined by two orthogonal aspects.
On the one hand, there is the inheritance relationship between a supertype and a subtype.
...
On the other hand, there is a relationship based on the type arguments. The prerequisite is that at least one of the involved type arguments is a wildcard. For example, Collection<? extends Number> is a supertype of Collection<Long>, because the type Long is a member of the type family that the wildcard " ? extends Number " denotes.
I think that the idea of the capture being a "new type X" sounds convincing, even though there is some handwaving involved: This happens somewhat "implicitly" in the resolution process, and not visible in the code, but I found it helpful, to some extent, when I wrote a library for types where all these questions came up...
Updated to elaborate this further, referring to the comment:
I mentioned that the types are not assignable "as of the usual constraints". Now one could argue about where these "constraints" come from. But they basically always have the same reason: The types are not assignable, because if they were assignable, the program would not be type safe. (Meaning that it would be possible to provoke a ClassCastException one way or the other).
Explaining where the type safety is lost here (and why a ClassCastException could be caused) involves some contortions. I'll try to show it here, based on the example that was referred to in the comment. Scroll down to tl;dr for a an example that has the same structure, but is much simpler.
The example from the comment was this:
import java.util.*;
import java.io.*;
class Ideone
{
public static class LinkTypeToken<T> extends TypeToken<List<T>> {}
public static class TypeToken<T> {}
public static void main (String[] args) throws java.lang.Exception {
LinkTypeToken<Number> listA = null;
TypeToken<List<Number>> listB = listA;
LinkTypeToken<? extends Number> listC = null;
TypeToken<? extends List<? extends Number>> listD = listC;
// error: incompatible types: LinkTypeToken<CAP#1> cannot be
// converted to TypeToken<List<? extends Number>>
// TypeToken<List<? extends Number>> listE = listC;
// ^
// where CAP#1 is a fresh type-variable:
// CAP#1 extends Number from capture of ? extends Number
TypeToken<List<? extends Number>> listE = listC;
}
}
The line that causes the compilation error here does so because if it was possible to assign these types, then one could do the following:
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
class IdeoneWhy
{
public static class ArrayListTypeToken<T> extends TypeToken<ArrayList<T>>
{
ArrayList<T> element = null;
void setElement(ArrayList<T> element)
{
this.element = element;
}
}
public static abstract class TypeToken<T>
{
abstract void setElement(T element);
}
public static void main(String[] args)
{
ArrayListTypeToken<? extends Number> listC = new ArrayListTypeToken<Integer>();
TypeToken<? extends List<? extends Number>> listD = listC;
// This is not possible:
//TypeToken<List<? extends Number>> listE = listC;
// But let's enforce it with a brutal cast:
TypeToken<List<? extends Number>> listE =
(TypeToken<List<? extends Number>>)(Object)listC;
// This throws a ClassCastException
listE.setElement(new LinkedList<Integer>());
}
}
So the fact that a TypeToken<List<? extends Number>> is not assignable from a TypeToken<? extends List<? extends Number>> indeed only serves the purpose of preventing a ClassCastException.
tl;dr :
The simpler variant is this:
import java.util.ArrayList;
import java.util.List;
public class WhySimpler
{
public static void main(String[] args)
{
List<Float> floats = new ArrayList<Float>();
// This is not possible
//List<Number> numbers = floats;
// Let's enforce it with a brutal cast:
List<Number> numbers = (List<Number>)(Object)floats;
Integer integer = 123;
// This is possible, because Integer is a Number:
numbers.add(integer);
// Now, we ended up placing an Integer into a list that
// may only contain Float values.
// So this will cause a ClassCastException:
Float f = floats.get(0);
}
}
Conversely, when the type is declared as List<? extends Number>, then the assignment is possible, because it is not possible to sneak an invalid type into such a list:
List<Float> floats = new ArrayList<Float>();
List<? extends Number> numbers = floats;
numbers.add(someInteger); // This is not possible
Long story short: It's all about type safety.
I was trying to solve a problem where I am not able to understand part of the answer.
Following is the class BackLister:
public class BackLister {
// INSERT HERE
{
List<T> output = new LinkedList<T>();
for (T t : input)
output.add(0, t);
return output;
}
}
The question asks which can be inserted at // INSERT HERE in the BackLister class to compile and run without error?
Following are the options:
A. public static <T> List<T> backwards(List<T> input)
B. public static <T> List<T> backwards(List<? extends T> input)
C. public static <T> List<T> backwards(List<? super T> input)
D. public static <T> List<? extends T> backwards(List<T> input)
E. public static <T> List<? super T> backwards(List<T> input)
F. public static <? extends T> List<T> backwards(List<T> input)
G. public static <? super T> List<T> backwards(List<T> input)
I understand that that A and B are correct, as for for (T t : input) to work the elements in input should be of type T or subtype of T.
But I am not able to understand why D and E options are correct?
I understand the following:
public static <T> List<? extends T> backwards(List<T> input)
means that the return type should be a List of T or subclass
of T.
public static <T> List<? super T> backwards(List<T> input) means that the return type should be a List of T or
superclass of T.
Could somebody help me understand it?
There is difference between each of them and I'm going to explain most of them. Let's start with our example. I use this class hierarchy:
class Food {}
class Apple extends Food {}
class Orange extends Food {}
class RedApple extends Apple {}
List<Food> listFood = new ArrayList<>();
List<Apple> listApple = new ArrayList<>();
List<Orange> listOrange = new ArrayList<>();
List<RedApple> listRedApple = new ArrayList<>();
ow start with first one:
A. public static <T> List<T> backwards(List<T> input)
This method will only accept List<T> and return List<T> and you can not send listApple and return listRedApple. (however your return list can contain RedApple because it extends Apple but type of list must be List<Apple> and nothing else)
B. public static <T> List<T> backwards(List<? extends T> input)
You can send listRedApple and return listApple but you know that listRedApple is "? extend Apple" so in the method body java recognize T as Apple. Then if you use can add elements in listRedApple which sent as argument, you can add Apple in listRedApple which is not true!!! so compiler avoid it and give compile error. In B you can only read elements (and get it as T) but you can not add anything to it.
C. public static <T> List<T> backwards(List<? super T> input)
You can send listApple and then in method body you can add anything extends Apple because compiler see T as Apple and in a list of anything which is super of T, you can add anything extends Apple.
However this time, you can not read anything because you don't know its type except you get it as Object. (It is a list of "? super T")
As you see here there is difference between ? super and ? extend. one of them give you write access and other give you read access. This is the real use of wildcard.
D. public static <T> List<? extends T> backwards(List<T> input)
E. public static <T> List<? super T> backwards(List<T> input)
If you send listApple then you return List<? extends Apple> but you can's assign it to any of listFood or listApple or listRedApple because List<? extends Apple> maybe contain Apple or RedApple or something else and we can't assign it to any List<T> because then we can add T to that list and maybe T and ? extends T is not same. this is same for both D and E. You can assign it to List<? extends Apple> for 'E and List<? super Apple> for D and send them to a method which need them as parameter.
F. public static <? extends T> List<T> backwards(List<T> input)
G. public static <? super T> List<T> backwards(List<T> input)
Give compile errors because wildcard can not used like this.
I hope this help you.
If something is wrong, any comment is appreciated.
Options D and E are valid because there exist a super-subtype relationships among generic types that allow you to define a larger set of types that the method can accept or return.
Consequently, the following is valid (D):
public static <T> List<? extends T> backwards(List<T> input) {
return List.of();
}
// there exist a super-subtype relationships among List<? extends Number> and List<Long>
List<? extends Number> list = backwards(List.<Long>of(1L, 2L));
because the type Long is a member of the type family that the wildcard ? extends Number denotes (the family of types that are subtypes of Number and the type Number itself).
The next code snippet is also valid (E):
public static <T> List<? super T> backwards(List<T> input) {
return List.of();
}
List<? super Long> ints = backwards(List.<Long>of(1L, 2L));
because the type Long is a member of the type family that the wildcard ? super Long denotes (the family of types that are supertypes of Long and the type Long itself).
So, your understanding is correct.
Further reading.
Below image describes the sub-typing relation in Generics:
List<T> is a subtype of List<? super T>
Also List<T> is a subtype of List<? extends T>
This is why options D and E are correct.
You can refer the page : https://docs.oracle.com/javase/tutorial/java/generics/subtyping.html
I have a class that can a list of take both a generic Number and a Double to perform a certain calculation.
This works fine:
public class Test<T extends Number> {
public void testGeneric(List<T> list){
doTest(list);
}
public void testExplicit(List<Double> list){
doTest(list);
}
public void doTest(List<? extends Number> testList){}
}
However, if the argument in question is a nested type, it doesn't compile anymore:
public class Test<T extends Number> {
public void testGeneric(List<List<T>> list){
doTest(list);
}
public void testExplicit(List<List<Double>> list){
doTest(list);
}
public void doTest(List<List<? extends Number>> testList){}
}
Note that the functionality of the list doesn't matter, the second List type can be anything generic, for instance a wrapper around the T type. I don't really see why there should be any difference. Is there a way around this?
Thanks!
As you apparently have realized, a List<Double> is not a subtype of List<Number>; you need the wildcards to make the inheritance "propagate" into the generic types: a List<Double> is a subtype of List<? extends Number>. However, the wildcards must go all the way from the outermost level: a List<X> is a subtype of List<List<? extends Number>> only if X is exactly a List<? extends Number>. If you want to accept other subtypes of List<? extends Number>, such as List<Double>, you need List<? extends List<? extends Number>>.