0

0

使用Java Stream高效实现分组并获取最大值映射

霞舞

霞舞

发布时间:2025-11-25 16:08:02

|

547人浏览过

|

来源于php中文网

原创

使用Java Stream高效实现分组并获取最大值映射

本文旨在探讨如何利用java stream api,特别是`collectors.tomap`结合合并函数,高效地将对象列表按指定属性分组,并为每个分组选出特定属性值最大的对象,最终生成一个映射(map)。我们将对比传统`groupingby`的局限性,并展示如何通过优化后的stream操作实现更简洁、高效的代码。

1. 场景概述与问题背景

在数据处理中,我们经常遇到这样的需求:给定一个包含多个对象的列表,需要根据某个属性对这些对象进行分组,并且在每个分组中,只保留满足特定条件(例如,某个数值属性最大)的一个对象。最终,我们希望将结果组织成一个Map,其中键是分组依据的属性值,值是选出的代表对象。

例如,我们有一个StudentGrade类,包含studentId、value(成绩)和date等信息:

import java.util.Date;

public class StudentGrade {
    int studentId;
    double value;
    Date date;

    // 构造函数
    public StudentGrade(int studentId, double value, Date date) {
        this.studentId = studentId;
        this.value = value;
        this.date = date;
    }

    // Getter 方法
    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,值是该学生对应的最高成绩StudentGrade对象。

2. 初始实现及局限性分析

一种常见的初步思路是使用Collectors.groupingBy结合Collectors.maxBy。这种方法首先将学生成绩按studentId分组,然后在每个分组中找到value最大的StudentGrade。然而,Collectors.maxBy返回的是Optional,这意味着我们需要额外的步骤来处理Optional并将其放入最终的Map中。

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

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

public class GradeProcessorInitial {

    public Map getMaxGradeByStudent(List grades) {
        Map> maxGradesOptional = grades.stream().collect(
            Collectors.groupingBy(
                StudentGrade::getStudentId,
                Collectors.maxBy(Comparator.comparing(StudentGrade::getValue)))
        );

        Map finalGrades = new HashMap<>();
        maxGradesOptional.entrySet().forEach(entry -> {
            entry.getValue().ifPresent(value -> finalGrades.put(entry.getKey(), value));
        });
        return finalGrades;
    }
}

上述方法虽然能够实现功能,但存在以下几点不足:

造次
造次

Liblib打造的AI原创IP视频创作社区

下载
  • 中间Optional层级: Collectors.maxBy返回Optional,需要后续处理来解包。
  • 额外的Map初始化和迭代: 需要创建一个新的HashMap,并对maxGradesOptional的entrySet进行迭代,将解包后的值放入新Map中,这增加了代码的复杂性和运行时的开销。
  • 不够Stream化: 整个过程并非完全在一个Stream管道中完成,有明显的“分段”操作。

3. 优化方案:使用 Collectors.toMap 与合并函数

Java Stream API提供了更简洁、高效的方式来解决这类问题,即使用Collectors.toMap的三参数版本,它允许我们指定一个合并函数(mergeFunction)来处理键冲突。当多个元素映射到同一个键时,合并函数会决定保留哪一个元素。

3.1 Collectors.toMap 的三参数版本

Collectors.toMap的签名如下: public static Collector> toMap(Function super T, ? extends K> keyMapper, Function super T, ? extends U> valueMapper, BinaryOperator mergeFunction)

  • keyMapper:用于从输入元素中提取键的函数。
  • valueMapper:用于从输入元素中提取值的函数。
  • mergeFunction:一个BinaryOperator,当多个元素映射到同一个键时,该函数用于合并这些元素的值。

3.2 优化后的实现

针对我们的StudentGrade问题,我们可以这样构建Collectors.toMap:

  1. keyMapper: 显然是StudentGrade::getStudentId,将studentId作为Map的键。
  2. valueMapper: 我们希望将整个StudentGrade对象作为Map的值,所以可以使用Function.identity()或x -> x。
  3. mergeFunction: 这是关键。当两个StudentGrade对象具有相同的studentId时,我们需要比较它们的value属性,并保留value更大的那个。这可以通过BinaryOperator.maxBy(Comparator.comparing(StudentGrade::getValue))来实现。
import java.util.*;
import java.util.function.BinaryOperator;
import java.util.function.Function;
import java.util.stream.Collectors;

public class GradeProcessorOptimized {

    public Map getMaxGradeByStudent(List grades) {
        return grades.stream()
            .collect(Collectors.toMap(
                StudentGrade::getStudentId,       // keyMapper: 以 studentId 为键
                Function.identity(),              // valueMapper: 以 StudentGrade 对象本身为值
                BinaryOperator.maxBy(Comparator.comparing(StudentGrade::getValue)) // mergeFunction: 遇到相同键时,保留 value 最大的对象
            ));
    }

    public static void main(String[] args) {
        List studentGrades = Arrays.asList(
            new StudentGrade(1, 85.0, new Date()),
            new StudentGrade(2, 90.0, new Date()),
            new StudentGrade(1, 92.0, new Date()), // 学生1的更高成绩
            new StudentGrade(3, 78.0, new Date()),
            new StudentGrade(2, 88.0, new Date()), // 学生2的较低成绩
            new StudentGrade(1, 70.0, new Date())
        );

        GradeProcessorOptimized processor = new GradeProcessorOptimized();
        Map maxGrades = processor.getMaxGradeByStudent(studentGrades);

        System.out.println("每个学生的最高成绩记录:");
        maxGrades.forEach((studentId, grade) ->
            System.out.println("学生ID: " + studentId + ", 最高成绩: " + grade.getValue())
        );
        // 验证学生1的最高成绩
        System.out.println("学生1的完整最高成绩记录: " + maxGrades.get(1));
    }
}

运行结果示例:

每个学生的最高成绩记录:
学生ID: 1, 最高成绩: 92.0
学生ID: 2, 最高成绩: 90.0
学生ID: 3, 最高成绩: 78.0
学生1的完整最高成绩记录: StudentGrade{studentId=1, value=92.0, date=Mon Jan 01 00:00:00 CST 2024} // 日期会是当前运行时的日期

3.3 优势分析

  • 简洁性: 代码更加紧凑和富有表达力,所有逻辑都在一个Stream管道中完成。
  • 效率: 避免了中间Optional对象的创建和额外的Map迭代,通常在一次遍历中即可完成所有操作。
  • 纯Stream操作: 完美地融入了Java Stream的声明式编程风格。
  • 直接输出所需类型: 直接返回Map,无需后续转换。

4. 注意事项与最佳实践

  • mergeFunction的重要性: Collectors.toMap的第三个参数——合并函数,是处理键冲突的关键。如果省略此参数(使用两参数版本),当存在重复键时,Stream会抛出IllegalStateException。因此,理解并正确使用合并函数至关重要。
  • BinaryOperator与Comparator: BinaryOperator是一个函数式接口,接受两个相同类型的参数并返回一个相同类型的结果。BinaryOperator.maxBy()和BinaryOperator.minBy()是其工厂方法,它们接受一个Comparator来定义比较规则。
  • 选择合适的Collector: 在处理分组和聚合问题时,Collectors.groupingBy和Collectors.toMap各有其适用场景。
    • groupingBy通常用于需要将所有相同键的元素收集到一个列表中(例如Map>),或者进行更复杂的下游收集器操作(例如Map计数,Map求平均值)。
    • toMap更适用于当每个键最终只对应一个值,并且需要处理键冲突(如选择最大/最小/最新等)的场景。
  • 可读性: 尽管代码更简洁,但对于不熟悉Stream API的开发者来说,三参数toMap可能需要一些时间来理解。适当的注释可以提高代码的可读性。

5. 总结

通过本文的讲解,我们了解了如何利用Java Stream API中的Collectors.toMap结合合并函数,优雅且高效地解决按属性分组并获取最大值的问题。相较于使用Collectors.groupingBy再进行额外处理的方案,优化后的方法不仅代码更简洁、可读性更强,而且在性能上也通常更优。掌握这种模式,能够帮助开发者编写出更符合现代Java编程范式的Stream处理代码。

相关专题

更多
java
java

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

837

2023.06.15

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

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

741

2023.07.05

java自学难吗
java自学难吗

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

736

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

微信聊天记录删除恢复导出教程汇总
微信聊天记录删除恢复导出教程汇总

本专题整合了微信聊天记录相关教程大全,阅读专题下面的文章了解更多详细内容。

36

2026.01.18

热门下载

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

精品课程

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

共23课时 | 2.7万人学习

C# 教程
C# 教程

共94课时 | 7万人学习

Java 教程
Java 教程

共578课时 | 47.7万人学习

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

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