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



我知道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>不会编译,因为列表是不变的。

我错过了什么?

这里没有协方差。相反,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从赋值的左侧传播到右侧,从而消除了重复。

参考:类型推理教程

JLS很好地说明了这种情况。
这不是初始化期间的协方差,因为它也适用于"经典"方法调用。
请注意,示例中使用的推理策略使其在 Java 8 中工作,但在 Java 7 中会失败。

18.5.2. 调用类型推理

请考虑上一节中的示例:

List<Number> ln = Arrays.asList(1, 2.0);

最具体的适用方法被确定为:

public static <T> List<T> asList(T... a)

为了完成方法调用的类型检查,我们必须确定它是否 与其目标类型兼容,List<Number>.

用于演示上一个适用性的绑定集 B2 部分是:

{ α <: Object, Integer <: α, Double <: α }

新的约束公式集如下所示:

{ ‹List<α> → List<Number>› }

此兼容性约束产生 α 的相等绑定,该 包含在新的绑定集 B3 中:

{ α <: Object, Integer <: α, Double <: α, α = Number }

这些边界很容易解决:

α = Number

最后,我们对声明的返回类型 asList 以确定方法调用的类型为 List; 显然,这与目标类型兼容。

这种推理策略不同于 Java SE 7 版的 Java® 语言规范,α它将基于以下 它的下限(甚至在考虑调用的目标之前 类型),正如我们在上一节中所做的那样。这将导致一个类型 错误,因为生成的类型不是 List 的子类型。

B/c 数字是这些子类的超类:

直接已知子类: 原子整数, 原子长, 大十进制, 大

整数, 字节, 双精度, 双累加器, 双加法器, 浮点数, 整数, 长, 长累加器, 长加法器, 短资料来源:https://docs.oracle.com/javase/9/docs/api/java/lang/Number.html

在你的第一个代码片段中,你把"水果"添加到"苹果"的篮子里(你不能这样做!),在第二种情况下,这不是真的。

当然,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

相关内容

  • 没有找到相关文章

最新更新