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

Java Stream分组求和取差续问:含Group、Collectors等技术点

提问:Java Streams 分组、收集器与映射转换续篇

本次问询涉及Group、Collectors、Map(Int to String)、Map(Map to Object)技术点,是我此前《Group, Sum byType then get diff using Java streams》问题的续篇。按照建议,我另开新帖而非更新原帖,此前问题已解决,现提出后续需求。

背景:我拥有如下数据集:

  • Sample(SampleId=1, SampleTypeId=1, SampleQuantity=5, SampleType=ADD)
  • Sample(SampleId=2, SampleTypeId=1, SampleQuantity=15, SampleType=ADD)
  • Sample(SampleId=3, SampleTypeId=1, SampleQuantity=10, SampleType=SUBTRACT)
  • (注:原数据输入截断,假设后续包含更多同类型或不同SampleTypeId的Sample数据)

需求:我需要实现以下操作:

  1. 按SampleTypeId对Sample数据分组
  2. 对每个分组内的ADD类型求和、SUBTRACT类型求和,计算两者的差值(ADD总和 - SUBTRACT总和)
  3. 将计算得到的差值(int类型)转换为String类型
  4. 最终把分组结果映射为自定义的结果对象(比如包含SampleTypeId、netQuantityStr等字段的SampleResult类)

解决方案

先搞定实体类

首先得确保咱们有对应的Sample实体和目标结果类SampleResult,代码如下:

// Sample实体类
public class Sample {
    private int sampleId;
    private int sampleTypeId;
    private int sampleQuantity;
    private SampleType sampleType;

    // 别忘了加构造器、getter和setter
    public enum SampleType { ADD, SUBTRACT }
}

// 最终要输出的结果类
public class SampleResult {
    private int sampleTypeId;
    private String netQuantityStr;

    public SampleResult(int sampleTypeId, String netQuantityStr) {
        this.sampleTypeId = sampleTypeId;
        this.netQuantityStr = netQuantityStr;
    }

    // getter和toString方法按需添加,方便调试和取值
}

用Java Streams一步到位(Java 12+)

如果你的项目用的是Java 12及以上,我强烈推荐用Collectors.teeing,它能同时处理两种收集逻辑,代码简洁又清晰:

import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

public class SampleProcessor {
    public static void main(String[] args) {
        List<Sample> samples = Arrays.asList(
                new Sample(1, 1, 5, Sample.SampleType.ADD),
                new Sample(2, 1, 15, Sample.SampleType.ADD),
                new Sample(3, 1, 10, Sample.SampleType.SUBTRACT),
                new Sample(4, 2, 8, Sample.SampleType.ADD),
                new Sample(5, 2, 3, Sample.SampleType.SUBTRACT)
        );

        // 核心逻辑:分组、计算差值、转换类型、封装对象
        Map<Integer, SampleResult> resultMap = samples.stream()
                // 按SampleTypeId分组
                .collect(Collectors.groupingBy(
                        Sample::getSampleTypeId,
                        // teeing收集器:同时做两个收集操作,再合并结果
                        Collectors.teeing(
                                // 收集当前分组内所有ADD类型的数量总和
                                Collectors.summingInt(s -> s.getSampleType() == Sample.SampleType.ADD ? s.getSampleQuantity() : 0),
                                // 收集当前分组内所有SUBTRACT类型的数量总和
                                Collectors.summingInt(s -> s.getSampleType() == Sample.SampleType.SUBTRACT ? s.getSampleQuantity() : 0),
                                // 计算差值,转String,封装成SampleResult
                                (addTotal, subtractTotal) -> {
                                    int netQuantity = addTotal - subtractTotal;
                                    return new SampleResult(sampleTypeId, String.valueOf(netQuantity));
                                }
                        )
                ));

        // 打印结果看看
        resultMap.forEach((typeId, result) -> System.out.println("SampleTypeId: " + typeId + ", 净数量(字符串): " + result.getNetQuantityStr()));
    }
}

关键技术点拆解

  • 分组(Group):Collectors.groupingBy(Sample::getSampleTypeId)是Stream分组的基础操作,把相同SampleTypeId的Sample归为一组。
  • Collectors.teeing:这个是Java 12的新特性,允许咱们对同一流执行两个独立的收集操作,最后把两个结果合并成一个,完美适配这里分别计算ADD和SUBTRACT总和的场景。
  • Int转String:用String.valueOf(netQuantity)就能轻松把int类型的差值转换成String,简单直接。
  • Map转自定义对象:最终的resultMap是Map<Integer, SampleResult>,把分组的键(SampleTypeId)和对应的结果对象绑定。如果需要把整个Map转换成List<SampleResult>,直接调用resultMap.values().stream().toList()就行。

Java 8兼容方案

如果你的项目还在使用Java 8(没有teeing收集器),咱们可以分步来写,可读性反而更好:

import java.util.*;
import java.util.stream.Collectors;

public class SampleProcessorJava8 {
    public static void main(String[] args) {
        List<Sample> samples = Arrays.asList(
                new Sample(1, 1, 5, Sample.SampleType.ADD),
                new Sample(2, 1, 15, Sample.SampleType.ADD),
                new Sample(3, 1, 10, Sample.SampleType.SUBTRACT),
                new Sample(4, 2, 8, Sample.SampleType.ADD),
                new Sample(5, 2, 3, Sample.SampleType.SUBTRACT)
        );

        // 第一步:先按SampleTypeId分组
        Map<Integer, List<Sample>> groupedByTypeId = samples.stream()
                .collect(Collectors.groupingBy(Sample::getSampleTypeId));

        // 第二步:遍历每个分组,计算差值并封装成SampleResult
        Map<Integer, SampleResult> resultMap = new HashMap<>();
        for (Map.Entry<Integer, List<Sample>> entry : groupedByTypeId.entrySet()) {
            int typeId = entry.getKey();
            List<Sample> groupSamples = entry.getValue();

            // 计算ADD总和
            int addTotal = groupSamples.stream()
                    .filter(s -> s.getSampleType() == Sample.SampleType.ADD)
                    .mapToInt(Sample::getSampleQuantity)
                    .sum();

            // 计算SUBTRACT总和
            int subtractTotal = groupSamples.stream()
                    .filter(s -> s.getSampleType() == Sample.SampleType.SUBTRACT)
                    .mapToInt(Sample::getSampleQuantity)
                    .sum();

            // 计算差值并转String,封装对象
            int netQuantity = addTotal - subtractTotal;
            resultMap.put(typeId, new SampleResult(typeId, String.valueOf(netQuantity)));
        }

        // 输出结果
        resultMap.forEach((typeId, result) -> System.out.println("SampleTypeId: " + typeId + ", 净数量(字符串): " + result.getNetQuantityStr()));
    }
}

这种分步写法虽然代码多一点,但逻辑清晰,Java 8完全兼容,新手也更容易理解。


内容的提问来源于stack exchange,提问作者user1778855

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.21 06:47:53