如何使用Java Stream groupingBy按科目分组并拆分不同类型Grade列表
Java Stream groupingBy 分组并拆分类型列表问题
数据源
现有包含科目(subject)、成绩日期(grade_date)、成绩/偏差值(grade)、类型(type,g为成绩,d1、d2为偏差)的数据源列表如下:
| subject | grade_date | grade | type |
|---|---|---|---|
| Math | 2022-03-01 | 1 | g |
| Math | 2022-03-01 | 0.5 | d1 |
| Math | 2022-03-01 | 0.5 | d2 |
| German | 2022-03-02 | 2 | g |
| German | 2022-03-02 | 0.3 | d1 |
| German | 2022-03-02 | 0.2 | d2 |
| German | 2022-05-01 | 1 | g |
| German | 2022-05-01 | 0.5 | d1 |
| German | 2022-05-01 | 0.4 | d2 |
目标结构
需要按subject分组,生成MapObject对象列表,类定义如下:
public class MapObject { private String subject; List<Grade> g = new ArrayList<>(); List<Grade> d1 = new ArrayList<>(); List<Grade> d2 = new ArrayList<>(); }
其中Grade类定义:
public class Grade { private Float grade; private LocalDate gradeDate; }
期望输出JSON
最终期望的JSON结构如下:
[ { "subject": "Math", "g": [ { "grade": 1.0, "gradeDate": "2022-03-01" } ], "d1": [ { "grade": 0.5, "gradeDate": "2022-03-01" } ], "d2": [ { "grade": 0.5, "gradeDate": "2022-03-01" } ] }, { "subject": "German", "g": [ { "grade": 2.0, "gradeDate": "2022-03-02" }, { "grade": 1.0, "gradeDate": "2022-05-01" } ], "d1": [ { "grade": 0.3, "gradeDate": "2022-03-02" }, { "grade": 0.5, "gradeDate": "2022-05-01" } ], "d2": [ { "grade": 0.2, "gradeDate": "2022-03-02" }, { "grade": 0.4, "gradeDate": "2022-05-01" } ] } ]
尝试代码(存在问题)
我尝试了以下代码,但无法将列表拆分为g、d1、d2三个独立列表:
var temp = myList.stream() .collect(Collectors.groupingBy(SourceEntity::getSubject, Collectors.mapping(s -> { return new Grade(s.getGrade(), s.getGradeDate()); }, Collectors.toList()))) .entrySet() .stream() .map(x -> new MapObject(x.getKey(), x.getValue())) .collect(Collectors.toList());
解决方案
当前代码只按subject分组,没有区分type类型,导致无法拆分出三个独立列表。以下是两种可行的解决方法:
方案1:双重分组后构建MapObject
先按subject和type进行双重分组,再将分组结果转换为目标MapObject结构:
var result = myList.stream() .collect(Collectors.groupingBy(SourceEntity::getSubject, Collectors.groupingBy(SourceEntity::getType, Collectors.mapping(s -> new Grade(s.getGrade(), s.getGradeDate()), Collectors.toList())))) .entrySet() .stream() .map(entry -> { MapObject obj = new MapObject(); obj.setSubject(entry.getKey()); Map<String, List<Grade>> typeMap = entry.getValue(); obj.setG(typeMap.getOrDefault("g", Collections.emptyList())); obj.setD1(typeMap.getOrDefault("d1", Collections.emptyList())); obj.setD2(typeMap.getOrDefault("d2", Collections.emptyList())); return obj; }) .collect(Collectors.toList());
注:需要给MapObject添加对应的setter方法,或者调整构造函数支持传入各类型列表。
方案2:自定义收集器直接分配数据
使用Collector.of自定义收集逻辑,直接将同一subject下的不同type数据分配到对应列表:
var result = myList.stream() .collect(Collectors.groupingBy(SourceEntity::getSubject, Collector.of( MapObject::new, (obj, entity) -> { Grade grade = new Grade(entity.getGrade(), entity.getGradeDate()); switch (entity.getType()) { case "g" -> obj.getG().add(grade); case "d1" -> obj.getD1().add(grade); case "d2" -> obj.getD2().add(grade); } }, (obj1, obj2) -> { obj1.getG().addAll(obj2.getG()); obj1.getD1().addAll(obj2.getD1()); obj1.getD2().addAll(obj2.getD2()); obj1.setSubject(obj1.getSubject() != null ? obj1.getSubject() : obj2.getSubject()); return obj1; }))) .values() .stream() .collect(Collectors.toList());
这种方式无需额外分组转换,直接在收集阶段完成数据分类,逻辑更直接。
注意事项
- 确保
SourceEntity类提供对应的getter方法:getSubject()、getType()、getGrade()、getGradeDate() MapObject类需要添加getter方法(方案2依赖),或者根据现有代码结构调整构造逻辑- 若存在其他type值,可在switch中添加
default分支处理,避免数据丢失
内容的提问来源于stack exchange,提问作者C0d3R
相关产品推荐
相关产品推荐

