如何用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>>,要求:
- 先按
gradeAndClass、再按gender拆分合法数据 - 将非法数据(班级或性别不符合枚举定义的条目)归集到单独的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; } }
关键说明
- 一次遍历拆分合法/非法:用
partitioningBy先将原Map条目分为合法和非法两组,避免多次遍历原数据。 - 复合Key分组:对合法数据用
groupingBy结合自定义GroupKey,实现按班级+性别双层分组。 - 结果整理:将分组后的合法子Map收集到列表,再添加非法数据Map,得到最终的
List<Map<String, Student>>。 - 扩展性:新增班级或性别枚举值时,只需修改枚举类,无需修改分组逻辑,扩展性大幅提升。
内容的提问来源于stack exchange,提问作者guiqin
相关产品推荐
相关产品推荐

