Java Stream按acct分组求和无需新建Sample对象的优化方案
更优实现方案
你当前写法的核心开销来自merge阶段每合并一个元素就新建一次Sample对象,完全可以通过复用分组对应的汇总实例的方式避免重复创建对象,有两种常用实现,按需选择即可。
方案1:直接修改toMap的merge逻辑(最小改动)
不需要调整整体代码结构,只需要修改merge函数的逻辑,不再新建对象返回,而是把新元素的金额直接累加到Map中已存在的Sample实例上,修改后返回已有实例即可:
Map<String, Sample> map = collectedTxs.stream() .collect(Collectors.toMap( Sample::getAcct, Function.identity(), (existSample, newSample) -> { // 直接在已存入Map的实例上累加金额,无新对象创建 existSample.setAmt1(safeAdd(existSample.getAmt1(), newSample.getAmt1())); existSample.setAmt2(safeAdd(existSample.getAmt2(), newSample.getAmt2())); return existSample; }, LinkedHashMap::new ));
注意事项
- 要求Sample实体类为
amt1、amt2提供setter方法 - 该方案会修改原始集合中每个acct对应第一条数据的Sample字段值,适合允许修改原始元素的场景
- 可以抽通用方法做BigDecimal空指针防护:
private static BigDecimal safeAdd(BigDecimal a, BigDecimal b) { return (a == null ? BigDecimal.ZERO : a).add(b == null ? BigDecimal.ZERO : b); }
方案2:自定义collect逻辑(不修改原始元素+最少对象创建)
如果业务不允许修改原始集合中的Sample对象,可以自定义Stream的collect逻辑,每个acct分组仅在首次出现时新建1次汇总用的Sample实例,后续所有同组元素直接在这个汇总实例上累加,不会重复创建对象:
Map<String, Sample> sumMap = collectedTxs.stream() .collect(LinkedHashMap::new, (resultMap, currentItem) -> { Sample sumInstance = resultMap.get(currentItem.getAcct()); if (sumInstance == null) { // 每个分组仅新建1次汇总对象,不引用原始集合元素 sumInstance = new Sample( currentItem.getId(), currentItem.getAcct(), currentItem.getAmt1(), currentItem.getAmt2() ); resultMap.put(currentItem.getAcct(), sumInstance); } else { // 同组后续元素直接累加,无新对象创建 sumInstance.setAmt1(safeAdd(sumInstance.getAmt1(), currentItem.getAmt1())); sumInstance.setAmt2(safeAdd(sumInstance.getAmt2(), currentItem.getAmt2())); } }, LinkedHashMap::putAll );
方案优势
- 每个acct分组最多创建1个Sample对象,相比原实现每合并一个元素就new对象的开销低很多
- 汇总用的Sample是独立新建的实例,累加操作完全不会修改原始集合中的任何元素,兼容性更好
- 同样可以复用上面的safeAdd方法做空值防护
内容的提问来源于stack exchange,提问作者Alvin
相关产品推荐
相关产品推荐

