我知道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
Integer
是Number
的子类List<Integer>
因此它是扩展Number
的有效事物List
。