问题描述
我正在流式传输实现接口的类的对象。我想将它们收集为接口元素的列表,而不是实现类。
这对于 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())
,但如果您愿意,您可以强制 T
为 Dodo
:
Stream.<Dodo>of(new FancyDodo()).toList();
现在 T
是 Dodo
,toList
将返回 List<Dodo>
。
您能做的最好的事情是接受一个 List<? super T>
作为参数,并向其中添加流元素
实际上,这就是 Collector
正在做的事情。请注意 collect
如何接受 Collector<? super T,DoesntMatter,R>
,并返回 R
。逆变 ? super T
使您能够像这样使用 toList
收集器。另请注意,R
是 collect
的通用参数,这意味着 您可以决定 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
,它的解析方式与 T
的 Stream
不同。由于所需的返回类型为 T
,编译器将 Dodo
解析为 List<Dodo>
。