I believe this has to do with invocation contexts and widening reference conversion.
Basically, in that invocation context, the type of the argument 0
in Arrays.asList(0)
can be boxed to Integer
and then widened to Number
. When that happens, Arrays.asList(0)
has a return type of List<Number>
. Through the same process, that List<Number>
can be converted to List<? extends Number>
before being used as an argument to the outer Arrays.asList(..)
.
This is equivalent to using explicit type arguments in Java 7
List<List<? extends Number>> xs = Arrays.<List<? extends Number>>asList(Arrays.<Number>asList(0));
In Java 7, the type of an expression is the same regardless of where it is used, whether it's a standalone expression or used in assignment expression.
Java 8 introduced poly-expressions in which the type of the expression could be influenced by the target type of the expression.
For example, in the following assignment expression
List<Number> numbers = Arrays.asList(1);
The type of the expression Arrays.asList(1)
is the return type of the method being invoked which depends entirely on the generic type parameter. In this case, that type argument will be inferred as Integer
because the value 1
can be converted to Integer
through boxing conversion (primitives can't be used with generics). So the type of the expression is List<Integer>
.
In Java 7, this assignment expression would not compile because List<Integer>
cannot be assigned to List<Number>
. This could be fixed by providing an explicit type argument when invoking asList
List<Number> numbers = Arrays.<Number>asList(1);
in which case, the method invocation expects a Number
argument for its first parameter and the value 1
satisfies that.
In Java 8, the assignment expression is a poly expression
A method invocation expression is a poly expression if all of the
following are true:
The invocation appears in an assignment context or an invocation context (§5.2, §5.3).
If the invocation is qualified (that is, any form of MethodInvocation
except for the first), then the invocation elides TypeArguments
to the left of the Identifier.
The method to be invoked, as determined by the following subsections, is generic (§8.4.4) and has a return type that mentions at least one of the method's type parameters.
Being a poly expression, it can be influenced by the type of the variable it is being assigned to. And that's what happens. The generic type Number
influences the type argument inferred in the invocation of Arrays.asList(1)
.
Note how it wouldn't work in the following example
List<Number> numbers = ...;
List<Integer> integers = ...; // integers is not a poly expression
numbers = integers; // nope
So it's not covariance, but we get some of its benefits in some places.
Arrays.asList(0)
would return aList<Integer>
, andArrays.asList()
of that would return aList
whose elements areList<Integer>
, seems to me that the assignmentList<List<? extends Number>>
would actually be right... I'm almost certainly misunderstanding something though – ProprietyList<List<? extends Number>> ys = Arrays.<List<? extends Number>asList(Arrays.asList(0));
. It shows that even under Java 7 the assignment is correct and only an issue of the limited type-inference. – Smokeless