如何使用Stream API筛选出大于平均值的元素?能否不使用两次流、仅借助Collectors类实现该需求?
如何用单次流操作提取大于算术平均值的整数列表?
嘿,我懂你现在的需求!你当前的代码用了两次流操作来实现——先算平均值,再过滤出大于平均值的元素,但确实可以优化成只遍历一次源集合,而且完全能借助Collectors类的方法来完成,不用自己写太多复杂逻辑。
先说说为什么两次流不太理想:如果你的整数集合很大,两次遍历会额外消耗不少性能,毕竟遍历一次和两次的时间复杂度差了一倍呢。下面给你两种靠谱的解决方案,分Java版本来看:
方案一:用Java 12+的Collectors.teeing()(推荐)
Java 12引入的teeing收集器简直为这种场景量身定做!它能把流中的每个元素同时传递给两个不同的收集器,最后再把这两个收集器的结果合并成你想要的最终结果。这样整个过程只需要遍历一次源集合。
代码示例:
List<Integer> aboveAverage = values.stream() .collect(Collectors.teeing( // 第一个收集器:计算集合的算术平均值 Collectors.averagingInt(Integer::intValue), // 第二个收集器:把所有元素收集到列表里 Collectors.toList(), // 合并函数:用第一个收集器得到的平均值过滤第二个收集器的列表 (average, allElements) -> allElements.stream() .filter(num -> num > average) .collect(Collectors.toList()) ));
这个写法既简洁又高效,完全符合你的要求——只用一次流,全程依赖Collectors类的方法。
方案二:自定义Collector(兼容Java 8及以上)
如果你的项目还在用Java 8或者更低版本,没法用teeing,那可以自定义一个Collector,在一次遍历中同时记录总和、元素个数以及所有元素,最后再计算平均值并过滤出符合条件的元素。
自定义Collector的代码如下:
import java.util.*; import java.util.stream.Collector; import static java.util.stream.Collector.Characteristics; class AboveAverageCollector implements Collector<Integer, Map<String, Object>, List<Integer>> { @Override public Supplier<Map<String, Object>> supplier() { // 初始化容器:保存总和、元素个数、所有元素 return () -> { Map<String, Object> container = new HashMap<>(); container.put("sum", 0); container.put("count", 0); container.put("elements", new ArrayList<>()); return container; }; } @Override public BiConsumer<Map<String, Object>, Integer> accumulator() { // 累加阶段:更新总和、计数,同时添加元素到列表 return (container, num) -> { container.put("sum", (int) container.get("sum") + num); container.put("count", (int) container.get("count") + 1); ((List<Integer>) container.get("elements")).add(num); }; } @Override public BinaryOperator<Map<String, Object>> combiner() { // 并行流场景下合并两个容器的结果 return (container1, container2) -> { container1.put("sum", (int) container1.get("sum") + (int) container2.get("sum")); container1.put("count", (int) container1.get("count") + (int) container2.get("count")); ((List<Integer>) container1.get("elements")).addAll((List<Integer>) container2.get("elements")); return container1; }; } @Override public Function<Map<String, Object>, List<Integer>> finisher() { // 完成阶段:计算平均值,过滤出大于平均值的元素 return container -> { double average = (int) container.get("sum") / (double) (int) container.get("count"); return ((List<Integer>) container.get("elements")).stream() .filter(num -> num > average) .collect(Collectors.toList()); }; } @Override public Set<Characteristics> characteristics() { return Collections.emptySet(); } }
使用的时候直接调用:
List<Integer> aboveAverage = values.stream().collect(new AboveAverageCollector());
这个自定义Collector也能做到单次遍历,而且兼容低版本Java。
对比你原来的写法,这两种方案都避免了两次遍历集合的问题,性能上更优,也完全满足你借助Collectors类实现的需求~
内容的提问来源于stack exchange,提问作者Good York
相关产品推荐
相关产品推荐

