为什么 StreamEx 强制我在收集到列表时将“?扩展”添加到变量类型?
Why StreamEx force me to add "? extends" to variable type when collecting to list?
我注意到在低于标准 Java 的情况下,流比 StreamEx 更好地“计算”变量类型。
这很奇怪,因为人们喜欢 StreamEx 并在任何地方都使用它,但是代码却被“?”污染了。
我想使用 List<Class<?>
,但 StreamEx 强制我使用 List<? extends Class<?>>
。有人可以解释为什么 StreamEx 以这种方式工作吗?我能以某种方式将所需的变量类型与 StreamEx 一起使用吗?
private static List<? extends Class<?>>
fooList_StreamEx_Compiles(List<Integer> input) {
return StreamEx.of(input)
.map(x -> foo())
.toList();
}
private static List<Class<?>>
fooList_StreamEx_Error(List<Integer> input) {
return StreamEx.of(input)
.map(x -> foo())
// Error: incompatible types: java.util.List<java.lang.Class<capture#1 of ?>>
// cannot be converted to java.util.List<java.lang.Class<?>>
.toList();
}
private static List<Class<?>> fooList(List<Integer> input) {
return input
.stream()
.map(x -> foo())
.collect(Collectors.toList());
}
private static Class<?> foo() {
return String.class;
}
我正在使用 StreamEx 0.7.0 和 Java 11
这不是 StreamEx
问题。当您在 StreamEx
上使用 collect(Collectors.toList())
时,它同样有效。问题与 toList()
便利方法有关,标准 Stream
甚至不提供。 StreamEx
是个普遍问题,不怪
StreamEx
上的 toList
方法具有以下签名:
public List<T> toList()
在理想情况下,创建一个用 super-type 参数化的列表是合法的,即
public <R super T> List<R> toList()
但在创建 Java 的泛型时,此语法已被省略。 Collection
和 Stream
的 toArray
方法都有类似的限制;它们不能将结果数组的元素类型声明为集合元素类型的超类型。但是由于检查了实际数组的存储操作,这些方法只允许任何元素类型。对于 List
结果,要进行类型擦除,这是不可能的。
另一方面,当使用 collect(Collectors.toList())
时,由于 collect
的签名,可以创建具有超类型的列表:
<R,A> R collect(Collector<? super T,A,R> collector)
它允许传入一个 Collector
参数化的超类型 (? super T
),在大多数情况下将从目标类型推断出来。
toList
声明的这一限制与另一个限制相互作用,即对通配符类型的不合理处理。我不确定这个问题的原因是编译器还是规范,但在 map(x -> foo())
步骤,捕获了通配符类型,并且这种捕获的类型将被认为不同于任何其他捕获的通配符类型,即使当它来自同一来源时。
当我用 javac
编译你的代码时,它说:
error: incompatible types: List<Class<CAP#1>> cannot be converted to List<Class<?>>
.toList();
^
where CAP#1 is a fresh type-variable:
CAP#1 extends Object from capture of ?
1 error
CAP#1
是捕获类型。所有捕获的类型都会编号,以区分它们。如前所述,它们中的每一个都被认为是一种独特的类型,彼此不同。
Class<?>
是 Class<CAP#1>
的超类型,因此它与 collect
一起使用,它允许收集到使用该超类型参数化的列表,如上所述,但不适用于 toList
.
您可以通过使用 return 类型 List<? extends Class<?>>
来解决此问题,以表示列表的实际元素类型是 Class<?>
的子类型,但更好的选择是强制编译器不使用捕获的类型:
private static List<Class<?>> fooList_StreamEx_Solved(List<Integer> input) {
return StreamEx.of(input)
.<Class<?>>map(x -> foo())
.toList();
}
如果您不完全理解在这里插入显式类型的必要性,请不要担心,我并不是说 Java 编译器的行为在涉及通配符类型时是完全可以理解的……
我注意到在低于标准 Java 的情况下,流比 StreamEx 更好地“计算”变量类型。
这很奇怪,因为人们喜欢 StreamEx 并在任何地方都使用它,但是代码却被“?”污染了。
我想使用 List<Class<?>
,但 StreamEx 强制我使用 List<? extends Class<?>>
。有人可以解释为什么 StreamEx 以这种方式工作吗?我能以某种方式将所需的变量类型与 StreamEx 一起使用吗?
private static List<? extends Class<?>>
fooList_StreamEx_Compiles(List<Integer> input) {
return StreamEx.of(input)
.map(x -> foo())
.toList();
}
private static List<Class<?>>
fooList_StreamEx_Error(List<Integer> input) {
return StreamEx.of(input)
.map(x -> foo())
// Error: incompatible types: java.util.List<java.lang.Class<capture#1 of ?>>
// cannot be converted to java.util.List<java.lang.Class<?>>
.toList();
}
private static List<Class<?>> fooList(List<Integer> input) {
return input
.stream()
.map(x -> foo())
.collect(Collectors.toList());
}
private static Class<?> foo() {
return String.class;
}
我正在使用 StreamEx 0.7.0 和 Java 11
这不是 StreamEx
问题。当您在 StreamEx
上使用 collect(Collectors.toList())
时,它同样有效。问题与 toList()
便利方法有关,标准 Stream
甚至不提供。 StreamEx
是个普遍问题,不怪
StreamEx
上的 toList
方法具有以下签名:
public List<T> toList()
在理想情况下,创建一个用 super-type 参数化的列表是合法的,即
public <R super T> List<R> toList()
但在创建 Java 的泛型时,此语法已被省略。 Collection
和 Stream
的 toArray
方法都有类似的限制;它们不能将结果数组的元素类型声明为集合元素类型的超类型。但是由于检查了实际数组的存储操作,这些方法只允许任何元素类型。对于 List
结果,要进行类型擦除,这是不可能的。
另一方面,当使用 collect(Collectors.toList())
时,由于 collect
的签名,可以创建具有超类型的列表:
<R,A> R collect(Collector<? super T,A,R> collector)
它允许传入一个 Collector
参数化的超类型 (? super T
),在大多数情况下将从目标类型推断出来。
toList
声明的这一限制与另一个限制相互作用,即对通配符类型的不合理处理。我不确定这个问题的原因是编译器还是规范,但在 map(x -> foo())
步骤,捕获了通配符类型,并且这种捕获的类型将被认为不同于任何其他捕获的通配符类型,即使当它来自同一来源时。
当我用 javac
编译你的代码时,它说:
error: incompatible types: List<Class<CAP#1>> cannot be converted to List<Class<?>>
.toList();
^
where CAP#1 is a fresh type-variable:
CAP#1 extends Object from capture of ?
1 error
CAP#1
是捕获类型。所有捕获的类型都会编号,以区分它们。如前所述,它们中的每一个都被认为是一种独特的类型,彼此不同。
Class<?>
是 Class<CAP#1>
的超类型,因此它与 collect
一起使用,它允许收集到使用该超类型参数化的列表,如上所述,但不适用于 toList
.
您可以通过使用 return 类型 List<? extends Class<?>>
来解决此问题,以表示列表的实际元素类型是 Class<?>
的子类型,但更好的选择是强制编译器不使用捕获的类型:
private static List<Class<?>> fooList_StreamEx_Solved(List<Integer> input) {
return StreamEx.of(input)
.<Class<?>>map(x -> foo())
.toList();
}
如果您不完全理解在这里插入显式类型的必要性,请不要担心,我并不是说 Java 编译器的行为在涉及通配符类型时是完全可以理解的……