39

How come in java we cannot do:

List<List<? extends Number>> aList = new ArrayList<List<Number>>();

Even though this is OK:

List<? extends Number> aList = new ArrayList<Number>();

Compiler error message is:

Type mismatch: cannot convert from ArrayList<List<Number>> to List<List<? extends Number>>

Michael Myers
  • 188,989
  • 46
  • 291
  • 292

5 Answers5

74

In Java, if Car is a derived class of Vehicle, then we can treat all Cars as Vehicles; a Car is a Vehicle. However, a List of Cars is not also a List of Vehicles. We say that List<Car> is not covariant with List<Vehicle>.

Java requires you to explicitly tell it when you would like to use covariance and contravariance with wildcards, represented by the ? token. Take a look at where your problem happens:

List<List<? extends Number>> l = new ArrayList<List<Number>>();
//        ----------------                          ------
// 
// "? extends Number" matched by "Number". Success!

The inner List<? extends Number> works because Number does indeed extend Number, so it matches "? extends Number". So far, so good. What's next?

List<List<? extends Number>> l = new ArrayList<List<Number>>();
//   ----------------------                    ------------
// 
// "List<? extends Number>" not matched by "List<Number>". These are
//   different types and covariance is not specified with a wildcard.
//   Failure.

However, the combined inner type parameter List<? extends Number> is not matched by List<Number>; the types must be exactly identical. Another wildcard will tell Java that this combined type should also be covariant:

List<? extends List<? extends Number>> l = new ArrayList<List<Number>>();
John Feminella
  • 303,634
  • 46
  • 339
  • 357
  • Seems like List> on the LHS would better match the poster's intent. – erickson Apr 14 '09 at 15:29
  • @erickson But `List>` gives the compilation error `incompatible types: ArrayList> cannot be converted to List>`. It's counter intuitive (to me), but you can't use List> on the LHS when `new ArrayList>();` is on the RHS. Things get tricky with multi-level wildcards. See [What is the difference between extends Base> and ?](https://stackoverflow.com/q/60536437/2985643) for a recent related question. – skomisa Mar 11 '20 at 17:34
3

I'm not very familiar with Java syntax but it seems that your issue is this:

Covariance & Contravariance

eulerfx
  • 36,769
  • 7
  • 61
  • 83
3

You should definitely use the ? type wildcard when appropriate, do not avoid it as a general rule. For example:

public void doThingWithList(List<List<? extends Number>> list);

allows you to pass a List<Integer> or a List<Long>.

public void doThingWithList(List<List<Number>> list);

allows you to only pass arguments declared as List<Number>. A small distinction, yes, but using the wildcard is powerful and safe. Contrary to how it may seem, a List<Integer> is not a subclass, or is not assignable, from List<Number>. Nor is List<Integer> a subclass of List<? extends Number, which is why the code above does not compile.

Steve Reed
  • 2,481
  • 2
  • 20
  • 20
2

Your statement does not compile because List<? extends Number> is not the same type as List<Number>. The former is a supertype of the latter.

Have you tried this? Here I'm expressing that the List is covariant in its type argument, so it will accept any subtype of List<? extends Number> (which includes List<Number>).

List<? extends List<? extends Number>> aList = new ArrayList<List<Number>>();

Or even this. Here the type parameter for the ArrayList on the right-hand side is the same as the type parameter on the left-hand side, so variance is not an issue.

List<List<? extends Number>> aList = new ArrayList<List<? extends Number>>();

You should be able to just say

List<List<Number>> aList = new ArrayList<List<Number>>();

I tend to avoid the ? type wildcard whenever possible. I find that the expense incurred in type annotation is not worth the benefit.

Apocalisp
  • 34,834
  • 8
  • 106
  • 155
  • These two suggestions will work, but it still doesn't explain the fact that the original statement doesn't work. –  Apr 14 '09 at 02:49
-2
List<List<? extends Number>> aList = new ArrayList<List<? extends Number>>();
aList.add(new ArrayList<Integer>());
Alex
  • 1