引入 Java 流的主要动机之一是允许并行操作。这导致了对 Java 流的操作的要求,例如map
and filter
独立于流中项目或其周围项目的位置。这样做的优点是可以轻松分割流以进行并行处理。它的缺点是使某些操作变得更加复杂。
因此,简单的答案是,没有简单的方法可以执行诸如获取第 n 个项目或将每个项目映射到所有先前项目的总和之类的操作。
实现您的要求的最直接的方法是使用您正在流式传输的列表的索引:
List<String> list = ...;
return IntStream.range(0, list.size())
.filter(n -> n % 3 == 0)
.mapToObj(list::get)
.toList();
更复杂的解决方案是创建一个自定义收集器,将每个第 n 项收集到列表中。
class EveryNth<C> {
private final int nth;
private final List<List<C>> lists = new ArrayList<>();
private int next = 0;
private EveryNth(int nth) {
this.nth = nth;
IntStream.range(0, nth).forEach(i -> lists.add(new ArrayList<>()));
}
private void accept(C item) {
lists.get(next++ % nth).add(item);
}
private EveryNth<C> combine(EveryNth<C> other) {
other.lists.forEach(l -> lists.get(next++ % nth).addAll(l));
next += other.next;
return this;
}
private List<C> getResult() {
return lists.get(0);
}
public static Collector<Integer, ?, List<Integer>> collector(int nth) {
return Collector.of(() -> new EveryNth(nth),
EveryNth::accept, EveryNth::combine, EveryNth::getResult));
}
这可以如下使用:
Stream.of("Anne", "Bill", "Chris", "Dean", "Eve", "Fred", "George")
.parallel().collect(EveryNth.collector(3)).toList();
返回结果["Anne", "Dean", "George"]
正如你所期望的那样。
即使采用并行处理,这也是一种非常低效的算法。它将接受的所有项目拆分为 n 个列表,然后只返回第一个。不幸的是,它必须在累积过程中保留所有项目,因为直到它们被组合起来才知道哪个列表是第 n 个列表。
考虑到收集器解决方案的复杂性和低效率,如果可以的话,我绝对建议您优先使用上面基于索引的解决方案。如果您没有使用支持的集合get
(例如,您通过了Stream
而不是一个List
)那么您需要使用以下方式收集流Collectors.toList
或使用EveryNth
解决方案如上。