3

我注意到在标准 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

4

1 回答 1

4

这不是StreamEx问题。当您在 上使用collect(Collectors.toList())StreamEx,它同样有效。问题与标准甚至没有提供的toList()便利方法有关。Stream这是一个普遍的问题,StreamEx不应该归咎于此。

toListon 方法具有以下StreamEx签名:

public List<T> toList()

在一个完美的世界中,创建一个使用超类型参数化的列表是合法的,即

public <R super T> List<R> toList()

但是在创建 Java 的泛型时,此语法已被省略。两者的toArray方法,都Collection受到Stream类似的限制;他们不能将结果数组的元素类型声明为集合元素类型的超类型。但是由于检查了实际数组的存储操作,这些方法只允许任何元素类型。因此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.

您可以通过使用返回类型来解决此问题List<? extends Class<?>>,以表示列表的实际元素类型是 的子类型Class<?>,但更好的选择是强制编译器不使用捕获的类型:

private static List<Class<?>> fooList_StreamEx_Solved(List<Integer> input) {
    return StreamEx.of(input)
            .<Class<?>>map(x -> foo())
            .toList();
}

如果您没有完全理解在此处插入显式类型的必要性,请不要担心,我并不是说当涉及通配符类型时,Java 编译器的行为是完全可以理解的……</p>

于 2020-09-18T12:48:26.543 回答