如何用Java Stream优化Measurement列表的加权平均计算?
优化Java 17下加权平均计算的实现方案
问题背景
给定如下Measurement类:
@Getter @AllArgsConstructor public class Measurement { private BigDecimal rawValue; private BigDecimal weighting; }
需要处理List<Measurement>计算加权平均,步骤为:
- 将每个
rawValue与对应的weighting相乘 - 求和所有加权后的原始值
- 将加权值总和除以所有权重的总和
当前实现使用两次流分别计算两个总和,不够简洁,需优化为Java 17环境下更高效简洁的实现。
优化方案一:使用Collectors.teeing一次遍历完成计算
Java 12引入的Collectors.teeing可在单次流处理中同时收集两个结果,完美适配此场景——一次遍历即可同时计算加权值总和与权重总和,避免重复遍历集合。
完整实现代码
import java.math.BigDecimal; import java.util.List; import java.util.stream.Collectors; public class WeightedAverageCalculator { public BigDecimal calculateWeightedAverage(List<Measurement> measurements) { if (measurements == null || measurements.isEmpty()) { return BigDecimal.ZERO; // 可根据业务需求改为抛出异常或返回null } return measurements.stream() .collect(Collectors.teeing( // 收集器1:计算rawValue*weighting的总和 Collectors.reducing(BigDecimal.ZERO, m -> m.getRawValue().multiply(m.getWeighting()), BigDecimal::add), // 收集器2:计算所有权重的总和 Collectors.reducing(BigDecimal.ZERO, Measurement::getWeighting, BigDecimal::add), // 合并两个结果:加权值总和 / 权重总和 (weightedSum, weightSum) -> { if (weightSum.compareTo(BigDecimal.ZERO) == 0) { return BigDecimal.ZERO; // 避免除以零,按需调整逻辑 } // 可根据业务需求更换舍入模式,例如ROUND_HALF_EVEN return weightedSum.divide(weightSum, BigDecimal.ROUND_HALF_UP); })); } }
关键说明
Collectors.teeing:每个流元素会被同时传入两个收集器处理,最终合并两个收集结果。- 边界处理:提前判空避免无效计算,加入权重总和为零的判断防止算术异常。
- 舍入模式:
BigDecimal.divide必须指定舍入规则,示例中用ROUND_HALF_UP(四舍五入),可根据业务调整。
优化方案二:自定义容器类遍历计算
若偏好传统遍历方式,可自定义一个简单容器存储两个总和,单次遍历完成计算:
import java.math.BigDecimal; import java.util.List; public class WeightedAverageCalculator { private static class SumContainer { private BigDecimal weightedSum = BigDecimal.ZERO; private BigDecimal weightSum = BigDecimal.ZERO; private void accumulate(Measurement measurement) { weightedSum = weightedSum.add(measurement.getRawValue().multiply(measurement.getWeighting())); weightSum = weightSum.add(measurement.getWeighting()); } } public BigDecimal calculateWeightedAverage(List<Measurement> measurements) { if (measurements == null || measurements.isEmpty()) { return BigDecimal.ZERO; } SumContainer container = new SumContainer(); measurements.forEach(container::accumulate); if (container.weightSum.compareTo(BigDecimal.ZERO) == 0) { return BigDecimal.ZERO; } return container.weightedSum.divide(container.weightSum, BigDecimal.ROUND_HALF_UP); } }
这个方案逻辑直观,同样只遍历一次集合,适合不需要使用流API的场景。
内容的提问来源于stack exchange,提问作者Robert Strauch
相关产品推荐
相关产品推荐

