如何用Java Stream groupingBy实现LineItem到PieSlice的映射转换
解决方案
要实现将List<LineItem>转换为Map<Category, PieSlice>的需求,我们可以分两步完成:先计算所有金额的总和,再按分类分组计算每组的金额总和并生成对应的PieSlice对象(包含百分比)。
步骤1:计算全局总金额
首先需要算出所有LineItem的amount总和,这是计算百分比的基础:
BigDecimal totalAmount = lineItemList.stream() .map(LineItem::getAmount) .reduce(BigDecimal.ZERO, BigDecimal::add);
步骤2:按分类分组并生成PieSlice
接下来通过Stream API分组计算每个分类的金额总和,再将结果转换为目标Map:
// 先按分类分组,计算每个分类的金额总和 Map<Category, BigDecimal> categoryTotalMap = lineItemList.stream() .collect(Collectors.groupingBy( LineItem::getCategory, Collectors.reducing(BigDecimal.ZERO, LineItem::getAmount, BigDecimal::add) )); // 转换为目标Map<Category, PieSlice> Map<Category, PieSlice> pieSliceMap = categoryTotalMap.entrySet().stream() .collect(Collectors.toMap( Map.Entry::getKey, entry -> { BigDecimal categoryTotal = entry.getValue(); // 处理总金额为0的边界情况,避免除法异常 BigDecimal percentage = totalAmount.compareTo(BigDecimal.ZERO) == 0 ? BigDecimal.ZERO : categoryTotal.divide(totalAmount, 2, RoundingMode.HALF_UP); PieSlice slice = new PieSlice(); slice.setValue(categoryTotal); slice.setPercentage(percentage); return slice; } ));
完整示例验证
结合你给出的测试数据,完整代码如下:
import java.math.BigDecimal; import java.math.RoundingMode; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.stream.Collectors; public class LineItemConverter { public static void main(String[] args) { List<LineItem> lineItemList = new ArrayList<>(); LineItem dog1 = new LineItem(); LineItem dog2 = new LineItem(); LineItem cow1 = new LineItem(); dog1.setCategory(Category.Dogs); dog2.setCategory(Category.Dogs); cow1.setCategory(Category.Cows); dog1.setAmount(BigDecimal.valueOf(5.50)); dog2.setAmount(BigDecimal.valueOf(3.50)); cow1.setAmount(BigDecimal.valueOf(1.00)); lineItemList.add(dog1); lineItemList.add(dog2); lineItemList.add(cow1); // 计算总金额 BigDecimal totalAmount = lineItemList.stream() .map(LineItem::getAmount) .reduce(BigDecimal.ZERO, BigDecimal::add); // 生成目标Map Map<Category, PieSlice> result = lineItemList.stream() .collect(Collectors.groupingBy( LineItem::getCategory, Collectors.reducing(BigDecimal.ZERO, LineItem::getAmount, BigDecimal::add) )) .entrySet().stream() .collect(Collectors.toMap( Map.Entry::getKey, entry -> { PieSlice slice = new PieSlice(); slice.setValue(entry.getValue()); slice.setPercentage(entry.getValue().divide(totalAmount, 1, RoundingMode.HALF_UP)); return slice; } )); // 验证结果 result.forEach((category, slice) -> { System.out.printf("分类:%s,总金额:%s,百分比:%s%n", category, slice.getValue(), slice.getPercentage()); }); } }
运行后输出:
分类:Dogs,总金额:9.00,百分比:0.9 分类:Cows,总金额:1.00,百分比:0.1
关键细节说明
- 使用
Collectors.reducing作为groupingBy的下游收集器,直接在分组时完成金额累加,避免生成中间的List<LineItem>。 - 处理总金额为0的边界情况:当所有
LineItem的金额总和为0时,直接将百分比设为0,避免除法异常。 BigDecimal的除法需要指定精度和舍入模式,示例中使用RoundingMode.HALF_UP(四舍五入),你可以根据需求调整小数位数和舍入规则。
内容的提问来源于stack exchange,提问作者hotmeatballsoup
相关产品推荐
相关产品推荐

