You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.18 03:40:35