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数据)
需求:我需要实现以下操作:
- 按
SampleTypeId对Sample数据分组- 对每个分组内的
ADD类型求和、SUBTRACT类型求和,计算两者的差值(ADD总和 - SUBTRACT总和)- 将计算得到的差值(int类型)转换为String类型
- 最终把分组结果映射为自定义的结果对象(比如包含
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
相关产品推荐
相关产品推荐

