Java Stream如何单次遍历实现Data对象各属性分规则聚合计算
单次Stream遍历多属性独立聚合实现方案
核心思路:避免对流做多次迭代,通过自定义收集逻辑维护可变聚合容器,遍历过程中同步更新所有属性的聚合结果,遍历结束后直接返回组装好的目标对象,全程仅遍历一次列表,时间复杂度O(n)。
基础实现:直接使用Stream.collect三参数方法
JDK原生Stream的collect方法支持自定义收集逻辑,不需要引入额外依赖,是最直接的实现方式。
首先补全可用于聚合的Data类定义:
class Data { int prop1; int prop2; // 业务对象构造方法 public Data(int prop1, int prop2) { this.prop1 = prop1; this.prop2 = prop2; } // 聚合结果容器用无参构造 public Data() {} public int getProp1() { return prop1; } public void setProp1(int prop1) { this.prop1 = prop1; } public int getProp2() { return prop2; } public void setProp2(int prop2) { this.prop2 = prop2; } @Override public String toString() { return "Data(" + prop1 + "," + prop2 + ")"; } }
针对题目中prop1求max、prop2求sum的需求,实现代码如下:
import java.util.Arrays; import java.util.List; public class AggregateTest { public static void main(String[] args) { List<Data> list = Arrays.asList(new Data(1,2), new Data(3,4)); Data aggregateResult = list.stream().collect( // 第一步:初始化聚合容器,给不同属性设置对应规则的初始值 () -> { Data initContainer = new Data(); initContainer.setProp1(Integer.MIN_VALUE); // max规则初始值设为int类型最小值 initContainer.setProp2(0); // sum规则初始值设为0 // 如果有属性需要求min,初始值设为Integer.MAX_VALUE即可 return initContainer; }, // 第二步:遍历每个元素,按规则更新容器内的聚合值 (container, currentData) -> { // prop1 执行max聚合 container.setProp1(Math.max(container.getProp1(), currentData.getProp1())); // prop2 执行sum聚合 container.setProp2(container.getProp2() + currentData.getProp2()); }, // 第三步:并行流场景下的分块结果合并逻辑,顺序流不会执行该方法 (container1, container2) -> { container1.setProp1(Math.max(container1.getProp1(), container2.getProp1())); container1.setProp2(container1.getProp2() + container2.getProp2()); } ); System.out.println(aggregateResult); // 输出 Data(3,6),完全匹配预期结果 } }
该实现的特点:
- 真正单次遍历,流只会迭代原列表一次,没有额外遍历开销
- 规则完全自定义,除了sum、max、min外,还可以支持平均值、计数、自定义业务规则等任意聚合逻辑
- 同时兼容顺序流和并行流,只要合并逻辑编写正确即可
通用封装:适配任意实体类的多属性聚合
如果业务中频繁需要做这类聚合,可以把收集逻辑抽成通用工具,避免重复编写样板代码:
import java.util.stream.Collector; import java.util.function.BiConsumer; import java.util.function.Supplier; public class AggregateUtils { /** * 构造多属性聚合收集器 * @param containerSupplier 结果容器初始化逻辑 * @param aggregateLogic 单元素聚合逻辑:输入当前容器、当前遍历元素,更新容器值 * @param mergeLogic 并行场景下两个分块容器的合并逻辑 * @return 可直接传入Stream.collect的收集器 */ public static <T, R> Collector<T, ?, R> buildAggregator( Supplier<R> containerSupplier, BiConsumer<R, T> aggregateLogic, BiConsumer<R, R> mergeLogic ) { return Collector.of( containerSupplier, aggregateLogic, (c1, c2) -> { mergeLogic.accept(c1, c2); return c1; } ); } }
使用时直接传入对应逻辑即可,代码更简洁:
Data result = list.stream().collect(AggregateUtils.buildAggregator( () -> { Data init = new Data(); init.setProp1(Integer.MIN_VALUE); init.setProp2(0); return init; }, (c, curr) -> { c.setProp1(Math.max(c.getProp1(), curr.getProp1())); c.setProp2(c.getProp2() + curr.getProp2()); }, (c1, c2) -> { c1.setProp1(Math.max(c1.getProp1(), c2.getProp1())); c1.setProp2(c1.getProp2() + c2.getProp2()); } ));
避坑说明
- 不要通过多次调用Stream接口分别聚合单个属性(比如先遍历求prop1的max,再遍历求prop2的sum),这种写法会多次遍历列表,数据量大时性能损耗明显,不符合one shot流式处理的要求
- 如果原列表可能为空,需要在聚合完成后做空值校验,比如max规则的返回值如果还是初始值
Integer.MIN_VALUE,需要根据业务场景处理空结果,避免返回无效初始值 - 如果属性是引用类型(比如BigDecimal、String),只需要替换对应聚合逻辑即可,比如BigDecimal求和用
add()方法,求max通过compareTo()判断大小
内容的提问来源于stack exchange,提问作者gkatz
相关产品推荐
相关产品推荐

