Java 8按单字段分组并映射至多字段的技术实现问询
解决方案
刚好做过类似的需求,用Java 8 Stream的分组+自定义收集器就能完美解决。核心思路是先按userId把所有记录归到一组,再把每组内的多条记录合并成一个Person对象,同时聚合courses集合,顺便保留用户的姓名信息。
高效实现代码
先给你最优化的版本,避免重复遍历数据:
import java.util.*; import java.util.stream.Collectors; public class PersonRecordMerger { // 核心合并方法 public static List<Person> mergeDbRecords(List<Map<String, Object>> dbQueryResults) { return dbQueryResults.stream() // 按userId分组,同时用自定义收集器处理每组数据 .collect(Collectors.groupingBy( record -> (String) record.get("userId"), Collector.of( // 1. 供应商:创建临时容器,存Person和待聚合的courses () -> new AbstractMap.SimpleEntry<>(null, new HashSet<Courses>()), // 2. 累加器:处理组内每条记录 (tempContainer, record) -> { // 第一次处理该组时,初始化Person对象 if (tempContainer.getKey() == null) { Person person = new Person(); person.setUserId((String) record.get("userId")); person.setFirstName((String) record.get("firstName")); person.setLastName((String) record.get("lastName")); tempContainer.setKey(person); } // 将当前记录的course加入集合 tempContainer.getValue().add((Courses) record.get("course")); }, // 3. 组合器:并行流场景下合并两个容器(可选,但规范要写) (container1, container2) -> { if (container1.getKey() == null) return container2; container1.getValue().addAll(container2.getValue()); return container1; }, // 4. 终止器:把聚合好的courses赋值给Person,返回最终对象 tempContainer -> { tempContainer.getKey().setCourses(tempContainer.getValue()); return tempContainer.getKey(); } ) )) .values() // 取出所有分组后的Person对象 .stream() .collect(Collectors.toList()); } // 你的Person类(补充必要的getter/setter) static class Person { String userId; String firstName; String lastName; Set<Courses> courses = new HashSet<>(); public String getUserId() { return userId; } public void setUserId(String userId) { this.userId = userId; } public String getFirstName() { return firstName; } public void setFirstName(String firstName) { this.firstName = firstName; } public String getLastName() { return lastName; } public void setLastName(String lastName) { this.lastName = lastName; } public Set<Courses> getCourses() { return courses; } public void setCourses(Set<Courses> courses) { this.courses = courses; } } // 你的Courses类(必须重写equals和hashCode,否则HashSet无法去重) static class Courses { private String courseId; public Courses(String courseId) { this.courseId = courseId; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Courses courses = (Courses) o; return Objects.equals(courseId, courses.courseId); } @Override public int hashCode() { return Objects.hash(courseId); } } }
关键细节说明
- 分组逻辑:用
Collectors.groupingBy按userId分组,确保同一个用户的所有记录被归到一组。 - 自定义收集器:
Collector.of是核心,四个参数分别负责:- 初始化临时容器,用来暂存Person对象和待聚合的课程集合
- 遍历组内每条记录,初始化Person并把课程加入集合
- 并行流场景下合并两个容器的内容(如果不用并行流,这个逻辑基本不会触发,但规范上要实现)
- 最后把聚合好的课程集合赋值给Person,返回最终对象
- Courses类的坑:一定要重写
equals和hashCode,不然HashSet没法识别重复的课程,会导致集合里出现重复数据。 - 姓名一致性:默认假设同一个
userId下的firstName和lastName是一致的(符合你给的示例数据),如果实际场景中有不一致的情况,可以在累加器里加判断逻辑(比如取第一条记录的值,或者抛出异常提示数据问题)。
内容的提问来源于stack exchange,提问作者Rinsen S
相关产品推荐
相关产品推荐

