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
Integer
是 Number
的子类,因此 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
到右侧,消除重复。
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.
我知道 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
Integer
是 Number
的子类,因此 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
到右侧,消除重复。
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.