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

如何用Java Stream的groupingBy()拆分Map为多个子Map?

用Java Stream groupingBy高效拆分Map并归集非法数据

背景与需求

我有如下Student类(原代码中gradeAndClass和gender为枚举类型,为简化说明暂用字符串替代):

public class Student {
    private String gradeAndClass;
    private String gender;
    private String name;

    // 构造方法、getter等省略
}

持有一个Map<String, Student>,key为唯一字符串,value是Student对象。需要将其拆分为多个子Map<String, Student>,最终得到List<Map<String, Student>>,要求:

  1. 先按gradeAndClass、再按gender拆分合法数据
  2. 将非法数据(班级或性别不符合枚举定义的条目)归集到单独的Map中

示例数据

Map<String, Student> data = new HashMap<>();

data.put("key1", new Student("class_1", "Boy", "Jo"));
data.put("key2", new Student("class_2", "Girl", "Alice"));
data.put("key3", new Student("class_1", "Girl", "Amy"));
data.put("key4", new Student("class_2", "Girl", "May"));
data.put("key5", new Student("class_1", "Boy", "Oscar"));
data.put("key6", new Student("class_2", "Boy", "Jimmy"));
data.put("key7", new Student("illegal class name", "Boy", "err1"));
data.put("key8", new Student("class_2", "not supported", "err2"));

期望结果

  • 子Map 1(class_1 + Boy):
"key1", ["class_1", "Boy", "Jo"]
"key5", ["class_1", "Boy", "Oscar"]
  • 子Map 2(class_1 + Girl):
"key3", ["class_1", "Girl", "Amy"]
  • 子Map 3(class_2 + Boy):
"key6", ["class_2", "Boy", "Jimmy"]
  • 子Map 4(class_2 + Girl):
"key2", ["class_2", "Girl", "Alice"]
"key4", ["class_2", "Girl", "May"]
  • 子Map 5(非法数据):
"key7", ["illegal class name", "Boy", "err1"]
"key8", ["class_2", "not supported", "err2"]

原有方案问题

之前通过硬编码逐个过滤条件生成子Map,还要单独遍历收集非法数据,不仅效率低(多次遍历原Map),而且扩展性差(新增枚举值时要修改多处代码)。现在希望用Stream的groupingBy实现所有需求。

原有单个子Map生成代码:

Map<String, Student> submap = data.entrySet().stream()
    .filter(entry -> "class_1".equals(entry.getValue().getGradeAndClass()) &&
                     "Boy".equals(entry.getValue().getGender()))
    .collect(Collectors.toMap(
        Map.Entry::getKey,
        Map.Entry::getValue
    ));

解决方案

可以通过一次遍历结合partitioningBy和groupingBy实现需求,步骤如下:

1. 恢复枚举定义(贴近原代码)

先定义合法的班级和性别枚举,用于校验:

public enum GradeAndClass {
    CLASS_1("class_1"), CLASS_2("class_2");

    private final String value;
    GradeAndClass(String value) { this.value = value; }
    public static Optional<GradeAndClass> fromValue(String value) {
        return Arrays.stream(values())
                .filter(gc -> gc.value.equals(value))
                .findFirst();
    }
}

public enum Gender {
    BOY("Boy"), GIRL("Girl");

    private final String value;
    Gender(String value) { this.value = value; }
    public static Optional<Gender> fromValue(String value) {
        return Arrays.stream(values())
                .filter(g -> g.value.equals(value))
                .findFirst();
    }
}

2. 定义复合分组Key

用一个简单的复合类作为分组的Key,用来同时按班级和性别分组:

private static class GroupKey {
    private final GradeAndClass gradeAndClass;
    private final Gender gender;

    public GroupKey(GradeAndClass gradeAndClass, Gender gender) {
        this.gradeAndClass = gradeAndClass;
        this.gender = gender;
    }

    // 必须重写equals和hashCode,否则groupingBy无法正确分组
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        GroupKey groupKey = (GroupKey) o;
        return gradeAndClass == groupKey.gradeAndClass && gender == groupKey.gender;
    }

    @Override
    public int hashCode() {
        return Objects.hash(gradeAndClass, gender);
    }
}

3. 完整处理逻辑

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

public class StudentGrouping {
    public static void main(String[] args) {
        // 初始化示例数据
        Map<String, Student> data = new HashMap<>();
        data.put("key1", new Student("class_1", "Boy", "Jo"));
        data.put("key2", new Student("class_2", "Girl", "Alice"));
        data.put("key3", new Student("class_1", "Girl", "Amy"));
        data.put("key4", new Student("class_2", "Girl", "May"));
        data.put("key5", new Student("class_1", "Boy", "Oscar"));
        data.put("key6", new Student("class_2", "Boy", "Jimmy"));
        data.put("key7", new Student("illegal class name", "Boy", "err1"));
        data.put("key8", new Student("class_2", "not supported", "err2"));

        // 一次遍历,拆分合法/非法数据,同时对合法数据分组
        Map<Boolean, Map<GroupKey, Map<String, Student>>> partitioned = data.entrySet().stream()
                .collect(Collectors.partitioningBy(
                        entry -> {
                            // 判断是否合法:班级和性别都能匹配到枚举
                            Student s = entry.getValue();
                            return GradeAndClass.fromValue(s.getGradeAndClass()).isPresent() &&
                                    Gender.fromValue(s.getGender()).isPresent();
                        },
                        // 对合法数据按GroupKey分组,收集为Map<String, Student>
                        Collectors.groupingBy(
                                entry -> {
                                    Student s = entry.getValue();
                                    GradeAndClass gc = GradeAndClass.fromValue(s.getGradeAndClass()).get();
                                    Gender g = Gender.fromValue(s.getGender()).get();
                                    return new GroupKey(gc, g);
                                },
                                Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)
                        )
                ));

        // 整理结果:合法子Map列表 + 非法Map
        List<Map<String, Student>> result = new ArrayList<>();
        // 添加所有合法子Map
        result.addAll(partitioned.get(true).values());
        // 添加非法数据Map(如果有)
        Map<String, Student> illegalMap = data.entrySet().stream()
                .filter(entry -> !GradeAndClass.fromValue(entry.getValue().getGradeAndClass()).isPresent() ||
                        !Gender.fromValue(entry.getValue().getGender()).isPresent())
                .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
        if (!illegalMap.isEmpty()) {
            result.add(illegalMap);
        }

        // 打印验证结果
        int index = 1;
        for (Map<String, Student> map : result) {
            System.out.println("子Map " + index + ":");
            map.forEach((key, student) -> {
                System.out.printf("\"%s\", [\"%s\", \"%s\", \"%s\"]%n",
                        key, student.getGradeAndClass(), student.getGender(), student.getName());
            });
            index++;
        }
    }
}

// Student类(带getter)
class Student {
    private String gradeAndClass;
    private String gender;
    private String name;

    public Student(String gradeAndClass, String gender, String name) {
        this.gradeAndClass = gradeAndClass;
        this.gender = gender;
        this.name = name;
    }

    public String getGradeAndClass() { return gradeAndClass; }
    public String getGender() { return gender; }
    public String getName() { return name; }
}

关键说明

  1. 一次遍历拆分合法/非法:用partitioningBy先将原Map条目分为合法和非法两组,避免多次遍历原数据。
  2. 复合Key分组:对合法数据用groupingBy结合自定义GroupKey,实现按班级+性别双层分组。
  3. 结果整理:将分组后的合法子Map收集到列表,再添加非法数据Map,得到最终的List<Map<String, Student>>。
  4. 扩展性:新增班级或性别枚举值时,只需修改枚举类,无需修改分组逻辑,扩展性大幅提升。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.19 06:25:21