
本教程深入探讨如何利用java stream api高效地将对象列表按指定属性分组,并从每个分组中选出具有最大值的元素,最终生成一个映射(map)。我们将重点介绍如何通过`collectors.tomap`结合`binaryoperator.maxby`,以简洁且声明式的方式实现这一常见的数据处理需求,避免繁琐的中间操作和手动集合初始化。
在数据处理中,我们经常遇到需要从一个对象集合中,根据某个属性进行分组,并在每个分组内选出满足特定条件的元素。例如,给定一个学生成绩列表,我们希望找出每个学生的最高成绩记录。
假设我们有如下 StudentGrade 类,包含学生ID、成绩值和成绩日期:
import java.util.Date;
public class StudentGrade {
private int studentId;
private double value;
private Date date;
public StudentGrade(int studentId, double value, Date date) {
this.studentId = studentId;
this.value = value;
this.date = date;
}
public int getStudentId() {
return studentId;
}
public double getValue() {
return value;
}
public Date getDate() {
return date;
}
@Override
public String toString() {
return "StudentGrade{" +
"studentId=" + studentId +
", value=" + value +
", date=" + date +
'}';
}
}我们的目标是获取一个 Map<Integer, StudentGrade>,其中键是 studentId,值是该学生对应的最高成绩记录。
一种常见的思路是首先使用 Collectors.groupingBy 按 studentId 分组,然后在每个分组中找出最大值。这通常会导致一个 Map<Integer, Optional<StudentGrade>>,因为 Collectors.maxBy 返回的是 Optional。随后,我们还需要遍历这个映射,解包 Optional 并将其放入一个新的 HashMap 中。
立即学习“Java免费学习笔记(深入)”;
以下是这种方法的示例:
import java.util.*;
import java.util.stream.Collectors;
public class GradeProcessor {
public Map<Integer, StudentGrade> getMaxGradeByStudentTraditional(List<StudentGrade> grades) {
// 第一步:按 studentId 分组,并找出每个分组中的最大成绩(Optional)
Map<Integer, Optional<StudentGrade>> maxGradesOptional = grades.stream().collect(
Collectors.groupingBy(
StudentGrade::getStudentId,
Collectors.maxBy(Comparator.comparing(StudentGrade::getValue)))
);
// 第二步:遍历 Optional 映射,解包并放入新的 HashMap
Map<Integer, StudentGrade> finalGrades = new HashMap<>();
maxGradesOptional.entrySet().forEach(entry -> {
entry.getValue().ifPresent(value -> finalGrades.put(entry.getKey(), value));
});
return finalGrades;
}
}这种方法虽然可行,但存在以下局限性:
Java Stream API 提供了一个更简洁、更优雅的解决方案,即利用 Collectors.toMap 的第三个参数——合并函数(mergeFunction)。当 Collectors.toMap 遇到键冲突时,mergeFunction 会被调用来决定保留哪个值。这正是我们用来选择最大值的好机会。
Collectors.toMap 的典型签名是: Collectors.toMap(keyMapper, valueMapper, mergeFunction)
结合 BinaryOperator.maxBy,我们可以直接在键冲突时比较并选择具有最大 value 的 StudentGrade 对象。
import java.util.*;
import java.util.function.BinaryOperator;
import java.util.function.Function;
import java.util.stream.Collectors;
public class GradeProcessor {
public Map<Integer, StudentGrade> getMaxGradeByStudentOptimized(List<StudentGrade> grades) {
return grades.stream()
.collect(Collectors.toMap(
StudentGrade::getStudentId, // keyMapper: 以 studentId 作为键
Function.identity(), // valueMapper: 以 StudentGrade 对象本身作为值
BinaryOperator.maxBy(Comparator.comparing(StudentGrade::getValue)) // mergeFunction: 当键冲突时,比较两个 StudentGrade 的 value,保留较大的
));
}
public static void main(String[] args) {
GradeProcessor processor = new GradeProcessor();
List<StudentGrade> studentGrades = Arrays.asList(
new StudentGrade(1, 85.5, new Date(120, 0, 1)), // Jan 1, 2020
new StudentGrade(2, 90.0, new Date(120, 1, 15)), // Feb 15, 2020
new StudentGrade(1, 92.0, new Date(120, 2, 10)), // Mar 10, 2020 (Higher for student 1)
new StudentGrade(3, 78.0, new Date(120, 3, 5)), // Apr 5, 2020
new StudentGrade(2, 88.0, new Date(120, 4, 20)), // May 20, 2020 (Lower for student 2)
new StudentGrade(1, 89.0, new Date(120, 5, 1)), // Jun 1, 2020 (Lower for student 1)
new StudentGrade(3, 95.0, new Date(120, 6, 1)), // Jul 1, 2020 (Higher for student 3)
new StudentGrade(4, 80.0, new Date(120, 7, 1)) // Aug 1, 2020
);
System.out.println("原始成绩列表:");
studentGrades.forEach(System.out::println);
System.out.println("\n-------------------------------------\n");
Map<Integer, StudentGrade> maxGrades = processor.getMaxGradeByStudentOptimized(studentGrades);
System.out.println("每个学生的最高成绩记录:");
maxGrades.forEach((studentId, grade) ->
System.out.println("学生ID: " + studentId + ", 最高成绩: " + grade)
);
/* 预期输出:
学生ID: 1, 最高成绩: StudentGrade{studentId=1, value=92.0, date=Tue Mar 10 00:00:00 CST 2020}
学生ID: 2, 最高成绩: StudentGrade{studentId=2, value=90.0, date=Sat Feb 15 00:00:00 CST 2020}
学生ID: 3, 最高成绩: StudentGrade{studentId=3, value=95.0, date=Wed Jul 01 00:00:00 CST 2020}
学生ID: 4, 最高成绩: StudentGrade{studentId=4, value=80.0, date=Sat Aug 01 00:00:00 CST 2020}
*/
}
}代码解释:
这种方法将整个逻辑封装在一个 collect 操作中,代码更加简洁、易读,且避免了中间 Optional 对象的处理和额外的 HashMap 初始化。
通过掌握 Collectors.toMap 的 mergeFunction 参数,开发者可以更有效地利用 Java Stream API 来解决复杂的数据转换和聚合问题,编写出更简洁、更富有表达力的代码。
以上就是Java Stream API:按属性分组并获取最大值映射的优雅实践的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号