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

问题描述

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

这对于 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())

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

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

解决方法

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

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

重要的部分是? super T

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

另一方面,Stream<FancyDodo>的{​​{1}}的签名是:

Stream

因此,如果您为 toList() 执行它,您将得到一个 List<T> toList() ,它不能分配给 Stream<FancyDodo> 变量。

在这种情况下,我建议您直接使用 List<FancyDodo> 而不是 List<Dodo>

,

因为 Stream.toList 被声明为返回一个 List<T>

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

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

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

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

现在 TDodotoList 将返回 List<Dodo>


您能做的最好的事情是接受一个 List<? super T> 作为参数,并向其中添加流元素

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

,

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

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

toList() 不解析 T,它只是使用已经建立的 T,所以结果是 always List<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,它的解析方式与 TStream 不同。由于所需的返回类型为 T,编译器将 Dodo 解析为 List<Dodo>