I know that Lists in Java are Invariant.
So the second statement below gives a compilation error as expected
List<Integer> integers = Arrays.asList(1, 2, 3);
List<Number> numbers = integers;
However, all of these work fine
List<Integer> numbers1 = Arrays.asList(1, 2, 3);
List<? extends Number> numbers2 = Arrays.asList(1, 2, 3);
List<Number> numbers3 = Arrays.asList(1, 2, 3);
So my question is how does the last statement above compile?
I understand that Arrays.asList()
accepts the type from its caller, but I thought Arrays.asList(1,2,3)
whould resolve to the closest type List<Integer>
and setting it to List<Number>
would not compile, since Lists are Invariant.
What am I missing?
There is no covariance going on here. Instead, Java compiler uses the context of the call to Arrays.asList<T>
to infer the type T
that your program wanted to specify for the method call.
Prior to Java 8 you could specify the type explicitly, for example
List<Integer> numbers1 = Arrays.<Integer>asList(1, 2, 3);
List<? extends Number> numbers2 = Arrays.<? extends Number>asList(1, 2, 3);
List<Number> numbers3 = Arrays.<Number>asList(1, 2, 3);
Noting how the above code snippets repeat the type T
on both sides of the assignment, Java compiler set up inference rules to propagate T
from the left side of the assignment to the right side, eliminating the repetition.
Reference: Type Inference Tutorial.
The JLS illustrates very well this case.
This is not covariance during initialization as it will also work in "classic" method invocations.
Note that the inference strategy used in your example make it work in Java 8 but it would fail in Java 7.
18.5.2. Invocation Type Inference
Consider the example from the previous section:
List<Number> ln = Arrays.asList(1, 2.0);
The most specific applicable method was identified as:
public static <T> List<T> asList(T... a)
In order to complete type-checking of the method invocation, we must determine whether it is compatible with its target type,
List<Number>
.The bound set used to demonstrate applicability in the previous section, B2, was:
{ α <: Object, Integer <: α, Double <: α }
The new constraint formula set is as follows:
{ ‹List<α> → List<Number>› }
This compatibility constraint produces an equality bound for α, which is included in the new bound set, B3:
{ α <: Object, Integer <: α, Double <: α, α = Number }
These bounds are trivially resolved:
α = Number
Finally, we perform a substitution on the declared return type of asList to determine that the method invocation has type List; clearly, this is compatible with the target type.
This inference strategy is different than the Java SE 7 Edition of The Java® Language Specification, which would have instantiated α based on its lower bounds (before even considering the invocation's target type), as we did in the previous section. This would result in a type error, since the resulting type is not a subtype of List.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With