为什么我不能使用 Stream#toList 在 Java 16 中收集 class' 接口的列表?

Why can't I use Stream#toList to collect a list of a class' interface in Java 16?

我正在流式传输 class 实现接口的对象。我想将它们收集为界面元素列表,而不是实现 class.

使用 Java 16.0.1 的 Stream#toList 方法似乎不可能。例如在下面的代码中,最后一条语句将无法编译。

import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class WhyDodo {

    private interface Dodo { }

    private static class FancyDodo implements Dodo { }

    public static void main(String[] args) {
        final List<Dodo> dodos = Stream.of(new FancyDodo()).collect(Collectors.toList());
        final List<FancyDodo> fancyDodos = Stream.of(new FancyDodo()).toList();

        final List<Dodo> noFancyDodos = Stream.of(new FancyDodo()).toList();
    }
}

我们可以明确地将每个元素从 FancyDodo 转换为 Dodo。但至少为了简洁起见,我们也可以使用 .collect(Collectors.toList()).

为什么我不能使用 Stream#toList 在 Java16 中收集 class' 接口的列表?

如果有人有比显式转换更好的解决方案,我也很乐意听到:)

泛型类型参数解析一次发生一个方法调用。

Stream.of(new FancyDodo())总是T 解析为 FancyDodo,因此总是会导致 Stream<FancyDodo>.

toList()不解析T,只是使用已经建立的T,所以结果是alwaysList<FancyDodo>,并且 List<FancyDodo>List<Dodo> 不兼容。请参阅:“Is List<Dog> a subclass of List<Animal>? Why are Java generics not implicitly polymorphic?

collect(Collectors.toList())Collectors.toList() 中有一个不同的 T,可以与 StreamT 不同地解析。由于 List<Dodo>.

所需的 return 类型,编译器将 T 解析为 Dodo

.collect(Collectors.toList()) 有效,因为 collect 的签名是:

<R, A> R collect(Collector<? super T, A, R> collector);

重要的部分是 ? super T

这意味着 toList() 收集器可以解释为 Collector<Dodo,?,List<Dodo>(当您将 .collect() 的结果分配给 List<Dodo> 时),即使您的流类型是 Stream<FancyDodo>.

另一方面,StreamtoList()的签名是:

List<T> toList()

所以如果你为 Stream<FancyDodo> 执行它,你会得到一个 List<FancyDodo>,它不能分配给 List<Dodo> 变量。

在这种情况下,我建议您只使用 stream.collect(Collectors.toList()) 而不是 stream.toList()

因为Stream.toList声明为return一个List<T>:

default List<T> toList() { ... }

其中 T 是流的元素类型。我真的想不出另一种声明 toList 的方法,这样它就可以 return 您想要的列表类型。您能做的最好的事情就是接受一个 List<? super T> 作为 参数 ,并向其中添加流元素,但这种做法违背了流的“美学”——整个这一点是声明性的并且几乎没有状态。

您可以重写 您的代码 以使 toList return 成为您所需类型的列表的一种方法是指定 T 手动。由于 Stream.of(new FancyDodo()),现在 T 被推断为 FancyDodo,但如果需要,您可以强制 TDodo

Stream.<Dodo>of(new FancyDodo()).toList();

现在 TDodotoList 将 return 变成 List<Dodo>


The best you can do is to accept a List<? super T> as argument, and add the stream elements to it

实际上,这就是 Collector 正在做的事情。注意 collect 如何接受 Collector<? super T, DoesntMatter, R> 和 returns R。逆变 ? super T 使您能够像那样使用 toList 收集器。另请注意,Rcollect 的通用参数,这意味着 你可以决定 collect returns,因为只要你能提供一个收集 ? super TR.

的收集器