0

0

Java Stream API:按属性分组并获取最大值映射的优雅实践

心靈之曲

心靈之曲

发布时间:2025-11-25 15:21:01

|

705人浏览过

|

来源于php中文网

原创

Java Stream API:按属性分组并获取最大值映射的优雅实践

本教程深入探讨如何利用java stream api高效地将对象列表按指定属性分组,并从每个分组中选出具有最大值的元素,最终生成一个映射(map)。我们将重点介绍如何通过`collectors.tomap`结合`binaryoperator.maxby`,以简洁且声明式的方式实现这一常见的数据处理需求,避免繁琐的中间操作和手动集合初始化。

1. 场景描述与需求分析

在数据处理中,我们经常遇到需要从一个对象集合中,根据某个属性进行分组,并在每个分组内选出满足特定条件的元素。例如,给定一个学生成绩列表,我们希望找出每个学生的最高成绩记录。

假设我们有如下 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,其中键是 studentId,值是该学生对应的最高成绩记录。

2. 传统分组与最大值筛选方法及其局限性

一种常见的思路是首先使用 Collectors.groupingBy 按 studentId 分组,然后在每个分组中找出最大值。这通常会导致一个 Map>,因为 Collectors.maxBy 返回的是 Optional。随后,我们还需要遍历这个映射,解包 Optional 并将其放入一个新的 HashMap 中。

立即学习Java免费学习笔记(深入)”;

以下是这种方法的示例:

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

public class GradeProcessor {

    public Map getMaxGradeByStudentTraditional(List grades) {
        // 第一步:按 studentId 分组,并找出每个分组中的最大成绩(Optional)
        Map> maxGradesOptional = grades.stream().collect(
            Collectors.groupingBy(
                StudentGrade::getStudentId,
                Collectors.maxBy(Comparator.comparing(StudentGrade::getValue)))
        );

        // 第二步:遍历 Optional 映射,解包并放入新的 HashMap
        Map finalGrades = new HashMap<>();
        maxGradesOptional.entrySet().forEach(entry -> {
            entry.getValue().ifPresent(value -> finalGrades.put(entry.getKey(), value));
        });
        return finalGrades;
    }
}

这种方法虽然可行,但存在以下局限性:

Veggie AI
Veggie AI

Veggie AI 是一款利用AI技术生成可控视频的在线工具

下载
  • 需要两次映射操作:一次是 groupingBy,另一次是手动遍历 Optional 映射并填充新 HashMap。
  • 引入了 Optional 类型,需要额外的 ifPresent 处理,增加了代码的冗余。
  • 不够“流式化”,未能充分利用 Stream API 的声明式优势。

3. 优化方案:使用 Collectors.toMap 与 BinaryOperator.maxBy

Java Stream API 提供了一个更简洁、更优雅的解决方案,即利用 Collectors.toMap 的第三个参数——合并函数(mergeFunction)。当 Collectors.toMap 遇到键冲突时,mergeFunction 会被调用来决定保留哪个值。这正是我们用来选择最大值的好机会。

Collectors.toMap 的典型签名是: Collectors.toMap(keyMapper, valueMapper, mergeFunction)

  • keyMapper:用于从流元素中提取键的函数。
  • valueMapper:用于从流元素中提取值的函数。
  • mergeFunction:一个 BinaryOperator,当多个流元素映射到同一个键时,用于解决冲突。它接收两个值(旧值和新值),并返回应该保留的值。

结合 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 getMaxGradeByStudentOptimized(List 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 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 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}
        */
    }
}

代码解释:

  1. grades.stream():创建 StudentGrade 对象的流。
  2. Collectors.toMap(...):将流中的元素收集到一个 Map 中。
    • StudentGrade::getStudentId:这是 keyMapper。它告诉收集器,对于每个 StudentGrade 对象,使用其 studentId 作为 Map 的键。
    • Function.identity():这是 valueMapper。它表示对于每个 StudentGrade 对象,使用对象本身作为 Map 的值。
    • BinaryOperator.maxBy(Comparator.comparing(StudentGrade::getValue)):这是 mergeFunction。当两个或更多 StudentGrade 对象具有相同的 studentId(即键冲突)时,这个函数会被调用。
      • Comparator.comparing(StudentGrade::getValue) 创建了一个比较器,用于比较两个 StudentGrade 对象的 value 属性。
      • BinaryOperator.maxBy(...) 返回一个 BinaryOperator,它会使用提供的比较器来选择两个输入中“较大”的一个。在这里,“较大”意味着 value 更大的 StudentGrade 对象将被保留。

这种方法将整个逻辑封装在一个 collect 操作中,代码更加简洁、易读,且避免了中间 Optional 对象的处理和额外的 HashMap 初始化。

4. 总结与注意事项

  • 简洁性与效率:使用 Collectors.toMap 结合 BinaryOperator 是处理此类“按键分组并聚合”问题的简洁且高效方式。它避免了多步操作和临时集合,使得代码更具声明性。
  • Function.identity():当流中的元素本身就是我们希望作为 Map 值的对象时,Function.identity() 是一个非常方便的 valueMapper。
  • BinaryOperator 的灵活性:BinaryOperator 不仅可以用于 maxBy,还可以用于 minBy,或者自定义任何合并逻辑(例如,求和、连接字符串等),使其在处理键冲突时非常灵活。
  • 适用于其他聚合:除了获取最大值,你也可以轻松地修改 mergeFunction 来实现获取最小值 (BinaryOperator.minBy),或者根据其他标准(如最新日期)来选择对象。
  • 错误处理:如果 keyMapper 可能返回 null,或者 valueMapper 返回 null 且 Map 实现不支持 null 值,需要额外考虑。然而,在大多数按ID分组的场景中,这些通常不是问题。

通过掌握 Collectors.toMap 的 mergeFunction 参数,开发者可以更有效地利用 Java Stream API 来解决复杂的数据转换和聚合问题,编写出更简洁、更富有表达力的代码。

相关专题

更多
java
java

Java是一个通用术语,用于表示Java软件及其组件,包括“Java运行时环境 (JRE)”、“Java虚拟机 (JVM)”以及“插件”。php中文网还为大家带了Java相关下载资源、相关课程以及相关文章等内容,供大家免费下载使用。

841

2023.06.15

java正则表达式语法
java正则表达式语法

java正则表达式语法是一种模式匹配工具,它非常有用,可以在处理文本和字符串时快速地查找、替换、验证和提取特定的模式和数据。本专题提供java正则表达式语法的相关文章、下载和专题,供大家免费下载体验。

742

2023.07.05

java自学难吗
java自学难吗

Java自学并不难。Java语言相对于其他一些编程语言而言,有着较为简洁和易读的语法,本专题为大家提供java自学难吗相关的文章,大家可以免费体验。

737

2023.07.31

java配置jdk环境变量
java配置jdk环境变量

Java是一种广泛使用的高级编程语言,用于开发各种类型的应用程序。为了能够在计算机上正确运行和编译Java代码,需要正确配置Java Development Kit(JDK)环境变量。php中文网给大家带来了相关的教程以及文章,欢迎大家前来阅读学习。

397

2023.08.01

java保留两位小数
java保留两位小数

Java是一种广泛应用于编程领域的高级编程语言。在Java中,保留两位小数是指在进行数值计算或输出时,限制小数部分只有两位有效数字,并将多余的位数进行四舍五入或截取。php中文网给大家带来了相关的教程以及文章,欢迎大家前来阅读学习。

399

2023.08.02

java基本数据类型
java基本数据类型

java基本数据类型有:1、byte;2、short;3、int;4、long;5、float;6、double;7、char;8、boolean。本专题为大家提供java基本数据类型的相关的文章、下载、课程内容,供大家免费下载体验。

446

2023.08.02

java有什么用
java有什么用

java可以开发应用程序、移动应用、Web应用、企业级应用、嵌入式系统等方面。本专题为大家提供java有什么用的相关的文章、下载、课程内容,供大家免费下载体验。

430

2023.08.02

java在线网站
java在线网站

Java在线网站是指提供Java编程学习、实践和交流平台的网络服务。近年来,随着Java语言在软件开发领域的广泛应用,越来越多的人对Java编程感兴趣,并希望能够通过在线网站来学习和提高自己的Java编程技能。php中文网给大家带来了相关的视频、教程以及文章,欢迎大家前来学习阅读和下载。

16926

2023.08.03

Java JVM 原理与性能调优实战
Java JVM 原理与性能调优实战

本专题系统讲解 Java 虚拟机(JVM)的核心工作原理与性能调优方法,包括 JVM 内存结构、对象创建与回收流程、垃圾回收器(Serial、CMS、G1、ZGC)对比分析、常见内存泄漏与性能瓶颈排查,以及 JVM 参数调优与监控工具(jstat、jmap、jvisualvm)的实战使用。通过真实案例,帮助学习者掌握 Java 应用在生产环境中的性能分析与优化能力。

19

2026.01.20

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
Kotlin 教程
Kotlin 教程

共23课时 | 2.7万人学习

C# 教程
C# 教程

共94课时 | 7.1万人学习

Java 教程
Java 教程

共578课时 | 48.5万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号