为什么我不能使用 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();
    }
}

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

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

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


答案 1

.collect(Collectors.toList())之所以有效,是因为签名是:collect

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

重要的部分是? super T

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

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

List<T> toList()

因此,如果您为 a 执行它,您将获得一个 ,它不能分配给变量。Stream<FancyDodo>List<FancyDodo>List<Dodo>

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


答案 2

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

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

其中 是流的元素类型。我真的想不出另一种声明方式,以便它可以返回您想要的列表类型。你能做的最好的事情就是接受一个 as 参数,并添加 stream 元素,但这违背了流的“美学”——这样做的重点是声明性的,几乎没有状态。TtoListList<? super T>

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

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

现在是 ,将返回一个 。TDodotoListList<Dodo>


您可以做的最好的方法是接受 as 参数,并向其添加流元素List<? super T>

实际上,这就是正在做的事情。请注意如何接受 和 返回 。这种逆变使你能够使用这样的收集器。另请注意,这是 的泛型参数,这意味着您可以决定返回什么集合,只要您可以提供一个收集器来收集到 。CollectorcollectCollector<? super T, DoesntMatter, R>R? super TtoListRcollect? super TR


推荐