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

如何使用Java Stream groupingBy按科目分组并拆分不同类型Grade列表

Java Stream groupingBy 分组并拆分类型列表问题

数据源

现有包含科目(subject)、成绩日期(grade_date)、成绩/偏差值(grade)、类型(type,g为成绩,d1、d2为偏差)的数据源列表如下:

subjectgrade_dategradetype
Math2022-03-011g
Math2022-03-010.5d1
Math2022-03-010.5d2
German2022-03-022g
German2022-03-020.3d1
German2022-03-020.2d2
German2022-05-011g
German2022-05-010.5d1
German2022-05-010.4d2

目标结构

需要按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());

这种方式无需额外分组转换,直接在收集阶段完成数据分类,逻辑更直接。

注意事项

  1. 确保SourceEntity类提供对应的getter方法:getSubject()、getType()、getGrade()、getGradeDate()
  2. MapObject类需要添加getter方法(方案2依赖),或者根据现有代码结构调整构造逻辑
  3. 若存在其他type值,可在switch中添加default分支处理,避免数据丢失

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.17 21:30:56