Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Does Java List behave as a covariant type during initialisation?

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?

like image 686
abhijeetpawar Avatar asked Mar 30 '18 12:03

abhijeetpawar


2 Answers

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.

like image 85
Sergey Kalinichenko Avatar answered Nov 12 '22 08:11

Sergey Kalinichenko


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.

like image 41
davidxxx Avatar answered Nov 12 '22 09:11

davidxxx