如何使用jOOλ库对BigDecimal字段按属性分组汇总?
使用jOOλ实现BigDecimal字段的分组汇总
当然可以用jOOλ实现BigDecimal的分组汇总,毕竟财务场景下的精度要求确实不能用Double来凑合。下面我会一步步帮你把原来的Double代码改成BigDecimal版本,还会扩展到支持更多统计维度(比如计数、平均值、最大最小值)。
第一步:修改实体类
首先把A类里的Double x改成BigDecimal x,注意初始化时要用字符串构造BigDecimal来保证精度:
class A { final int w; final BigDecimal x; final int y; final int z; A(int w, BigDecimal x, int y, int z) { this.w = w; this.x = x; this.y = y; this.z = z; } // 添加上getter方便后续收集器使用 public BigDecimal getX() { return x; } }
第二步:基础求和实现
如果只需要求和,可以直接用Java标准库的Collectors.reducing配合jOOλ的分组API,不需要额外写类:
Map<Tuple2<Integer, Integer>, BigDecimal> sumMap = Seq.of( new A(1, new BigDecimal("1.01"), 1, 1), new A(1, new BigDecimal("2.09"), 3, 1), new A(1, new BigDecimal("8"), 6, 1), new A(2, new BigDecimal("119"), 7, 2), new A(1, new BigDecimal("3.01"), 4, 1), new A(1, new BigDecimal("4"), 4, 1), new A(1, new BigDecimal("5"), 5, 1)) .groupBy( t -> tuple(t.z, t.w), // 保持你原来的分组逻辑:按z和w分组 Tuple.collectors( Collectors.reducing( BigDecimal.ZERO, // 初始值 A::getX, // 提取BigDecimal字段 BigDecimal::add // 汇总逻辑:相加 ) ) ); // 输出结果 sumMap.entrySet().forEach(t -> { log.info("w={}, z={}, sumX={}", t.getKey().v2, t.getKey().v1, t.getValue()); });
第三步:扩展到完整统计(计数、平均、最大最小)
如果需要像DoubleSummaryStatistics那样的完整统计信息,我们可以自己实现一个BigDecimalSummaryStatistics类,再配合自定义Collector:
1. 实现统计类
class BigDecimalSummaryStatistics { private long count; private BigDecimal sum = BigDecimal.ZERO; private BigDecimal min; private BigDecimal max; public void accept(BigDecimal value) { count++; sum = sum.add(value); // 更新最小值 if (min == null || value.compareTo(min) < 0) { min = value; } // 更新最大值 if (max == null || value.compareTo(max) > 0) { max = value; } } // 合并两个统计结果(并行流时会用到) public BigDecimalSummaryStatistics combine(BigDecimalSummaryStatistics other) { if (other.count == 0) return this; this.count += other.count; this.sum = this.sum.add(other.sum); this.min = (this.min == null) ? other.min : this.min.min(other.min); this.max = (this.max == null) ? other.max : this.max.max(other.max); return this; } // Getter方法 public long getCount() { return count; } public BigDecimal getSum() { return sum; } public BigDecimal getMin() { return min; } public BigDecimal getMax() { return max; } // 计算平均值,注意指定精度和舍入模式避免异常 public BigDecimal getAverage() { return count == 0 ? BigDecimal.ZERO : sum.divide(BigDecimal.valueOf(count), 2, RoundingMode.HALF_UP); } }
2. 创建自定义Collector
Collector<A, ?, BigDecimalSummaryStatistics> bigDecimalStatsCollector = Collector.of( BigDecimalSummaryStatistics::new, // 初始化统计对象 (stats, a) -> stats.accept(a.getX()), // 处理每个元素 BigDecimalSummaryStatistics::combine // 合并结果 );
3. 用jOOλ分组并统计
Map<Tuple2<Integer, Integer>, BigDecimalSummaryStatistics> statsMap = Seq.of( new A(1, new BigDecimal("1.01"), 1, 1), new A(1, new BigDecimal("2.09"), 3, 1), new A(1, new BigDecimal("8"), 6, 1), new A(2, new BigDecimal("119"), 7, 2), new A(1, new BigDecimal("3.01"), 4, 1), new A(1, new BigDecimal("4"), 4, 1), new A(1, new BigDecimal("5"), 5, 1)) .groupBy( t -> tuple(t.z, t.w), Tuple.collectors(bigDecimalStatsCollector) ); // 输出完整统计信息 statsMap.entrySet().forEach(t -> { BigDecimalSummaryStatistics stats = t.getValue(); log.info("w={}, z={}, count={}, sumX={}, avgX={}, minX={}, maxX={}", t.getKey().v2, t.getKey().v1, stats.getCount(), stats.getSum(), stats.getAverage(), stats.getMin(), stats.getMax()); });
关键注意点
- 精度保证:始终用字符串构造BigDecimal,避免浮点数转BigDecimal时的精度丢失。
- 不可变性:BigDecimal是不可变类,汇总时必须用
add方法返回新实例,不能直接修改原有对象。 - 舍入模式:计算平均值时一定要指定舍入模式,否则当无法整除时会抛出
ArithmeticException,可以根据业务需求调整精度和舍入规则。
内容的提问来源于stack exchange,提问作者Leo
相关产品推荐
相关产品推荐

