Java List 在初始化期间是否表现为协变类型?

Does Java List behave as a covariant type during initialisation?

我知道 Java 中的列表是不变的。

所以下面的第二个语句给出了预期的编译错误

    List<Integer> integers = Arrays.asList(1, 2, 3);
    List<Number> numbers = integers;

然而,所有这些都工作正常

    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);

所以我的问题是上面最后一条语句是如何编译的?

我知道 Arrays.asList() 接受来自其调用者的类型,但我认为 Arrays.asList(1,2,3) 会解析为最接近的类型 List<Integer> 并将其设置为 List<Number> 不会编译,因为列表是不变的。

我错过了什么?

B/c Number 是这些子类的超类:

直接已知子类: AtomicInteger、AtomicLong、BigDecimal、BigInteger、Byte、Double、DoubleAccumulator、DoubleAdder、Float、Integer、Long、LongAccumulator、LongAdder、Short

来源:https://docs.oracle.com/javase/9/docs/api/java/lang/Number.html

在您的第一个代码片段中,您将 "fruits" 添加到 "apples" 的篮子中(您不能这样做!),在您的第二种情况下,这是不正确的。

当然,Arrays.asList(1, 2, 3) 会产生 List<Integer>。 那么,List<Integer> 是有效的 List<? extends Number>。你可以向自己证明这一点:

List<? extends Number> l1 = new ArrayList<>();
List<Integer> l2 = new ArrayList<>();
l1 = l2;

然而事实并非如此:

l2 = l1; // does not compile

IntegerNumber 的子类,因此 List<Integer> 是扩展 Number.

的有效 List

这里没有协方差。相反,Java 编译器使用调用 Arrays.asList<T> 的上下文来 推断 您的程序想要为方法调用指定的类型 T

在 Java 8 之前,您可以明确指定类型,例如

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);

注意上面的代码片段如何在赋值的两边重复类型 T,Java 编译器设置推理规则从赋值的左侧传播 T到右侧,消除重复。

参考:Type Inference Tutorial.

JLS 很好地说明了这种情况。
这不是初始化期间的协变,因为它也适用于 "classic" 方法调用。
请注意,您的示例中使用的推理策略使其在 Java 8 中有效,但在 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.