0

0

使用Java Stream API按多字段分组并筛选最新记录

花韻仙語

花韻仙語

发布时间:2025-09-12 10:40:01

|

197人浏览过

|

来源于php中文网

原创

使用java stream api按多字段分组并筛选最新记录

本教程将演示如何利用Java Stream API高效处理对象列表,根据对象的多个字段进行分组,并从每个分组中选取出满足特定条件的唯一记录。我们将以员工数据为例,展示如何通过Collectors.toMap结合自定义合并逻辑,实现按姓氏和名字组合筛选出最新薪资记录的需求。

业务场景概述

在实际开发中,我们经常会遇到需要处理包含重复数据的列表,但这些重复数据在某些关键字段上是唯一的。例如,一个员工列表可能包含同一个员工(通过姓氏和名字确定)在不同时间点的薪资记录。我们的目标是,对于每个唯一的员工组合(例如,相同的姓氏和名字),只保留其最新的一条记录。

假设我们有以下Employee类定义:

import lombok.AllArgsConstructor;
import lombok.Data;

import java.time.LocalDate;

@Data
@AllArgsConstructor
public class Employee {
    private String firstName;
    private String lastName;
    private double salary;
    private LocalDate date; // 使用LocalDate表示日期
}

以及一个包含示例数据的List

import java.util.ArrayList;
import java.util.List;
import java.time.LocalDate;

public class EmployeeData {
    public static List getSampleEmployees() {
        List employees = new ArrayList<>();
        employees.add(new Employee("John", "Smith", 10, LocalDate.of(2022, 9, 1)));
        employees.add(new Employee("John", "Smith", 20, LocalDate.of(2022, 10, 1)));
        employees.add(new Employee("John", "Smith", 5, LocalDate.of(2022, 11, 1)));
        employees.add(new Employee("Kelly", "Jones", 12, LocalDate.of(2022, 3, 1)));
        employees.add(new Employee("Sara", "Kim", 21, LocalDate.of(2022, 3, 1)));
        employees.add(new Employee("Sara", "Kim", 7, LocalDate.of(2022, 7, 1)));
        return employees;
    }
}

我们的目标是得到以下结果:

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

易标AI
易标AI

告别低效手工,迎接AI标书新时代!3分钟智能生成,行业唯一具备查重功能,自动避雷废标项

下载
  • "John", "Smith", 5, 2022-11-01 (最新日期)
  • "Kelly", "Jones", 12, 2022-03-01 (唯一记录)
  • "Sara", "Kim", 7, 2022-07-01 (最新日期)

使用 Java Stream API 解决问题

Java 8 引入的 Stream API 为处理集合数据提供了强大而简洁的工具。对于上述需求,我们可以利用Collectors.toMap的重载方法,该方法允许我们定义一个合并函数来处理键冲突。

核心思路

  1. 生成唯一的键: 对于每个Employee对象,我们需要一个唯一的键来表示一个特定的员工。在这个例子中,firstName和lastName的组合构成了这个键。
  2. 处理键冲突: 当两个或多个Employee对象生成相同的键时(即它们是同一个员工),我们需要一个策略来决定保留哪一个。在本例中,我们希望保留日期最新的那条记录。

示例代码

import java.util.Collection;
import java.util.List;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.time.LocalDate;

public class EmployeeFilterTutorial {

    public static void main(String[] args) {
        List employees = EmployeeData.getSampleEmployees();

        Collection filteredEmployees = employees.stream()
                .collect(Collectors.toMap(
                        // keyMapper: 定义如何从Employee对象中提取键
                        // 这里将firstName和lastName拼接作为键
                        employee -> employee.getFirstName() + employee.getLastName(),
                        // valueMapper: 定义如何从Employee对象中提取值
                        // Function.identity() 表示Employee对象本身作为值
                        Function.identity(),
                        // mergeFunction: 定义当两个键相同时如何合并值
                        // 比较两个Employee对象的日期,保留日期最新的那个
                        (existingEmployee, newEmployee) -> existingEmployee.getDate().isAfter(newEmployee.getDate()) ? existingEmployee : newEmployee
                ))
                .values(); // 获取Map中所有的值,即我们筛选后的Employee对象

        // 打印结果
        filteredEmployees.forEach(System.out::println);
    }
}

代码解析

  1. employees.stream(): 创建一个Employee对象的流。
  2. .collect(Collectors.toMap(...)): 这是核心操作。Collectors.toMap有多个重载方法,这里我们使用的是接受三个参数的版本:
    • keyMapper (employee -> employee.getFirstName() + employee.getLastName()): 这个函数负责从每个Employee对象中提取出用于作为Map键的部分。在这里,我们将firstName和lastName字符串拼接起来,形成一个唯一的员工标识符。
      • 注意事项: 字符串拼接作为键简单易行,但在某些情况下可能存在性能问题或潜在的哈希冲突风险(尽管对于人名组合通常足够)。更健壮的方法是定义一个包含firstName和lastName的自定义类作为键,并正确实现其equals()和hashCode()方法。
    • valueMapper (Function.identity()): 这个函数负责从Employee对象中提取出用于作为Map值的部分。Function.identity()是一个方便的静态方法,它表示将输入对象本身作为输出,即Employee对象本身将作为Map的值。
    • mergeFunction ((existingEmployee, newEmployee) -> existingEmployee.getDate().isAfter(newEmployee.getDate()) ? existingEmployee : newEmployee): 这是解决键冲突的关键。当keyMapper生成相同的键时,toMap会调用这个函数来决定保留哪个值。
      • existingEmployee:是Map中已经存在的值(即之前处理过的相同键的Employee对象)。
      • newEmployee:是当前正在处理的、与existingEmployee具有相同键的Employee对象。
      • 我们的逻辑是比较它们的date字段。如果existingEmployee的日期在newEmployee之后,则保留existingEmployee;否则,保留newEmployee。这确保了Map中最终存储的是具有最新日期的Employee记录。
  3. .values(): Collectors.toMap操作返回一个Map。我们只需要最终筛选出来的Employee对象,因此调用.values()方法获取Map中所有值的Collection。

运行结果

执行上述代码,将得到以下输出:

Employee(firstName=John, lastName=Smith, salary=5.0, date=2022-11-01)
Employee(firstName=Sara, lastName=Kim, salary=7.0, date=2022-07-01)
Employee(firstName=Kelly, lastName=Jones, salary=12.0, date=2022-03-01)

这正是我们期望的结果,对于每个唯一的员工组合,都成功筛选出了日期最新的那条记录。

总结与注意事项

  • Collectors.toMap的强大之处: 通过提供一个自定义的合并函数,Collectors.toMap可以非常灵活地处理键冲突,这使其成为根据特定条件从分组数据中选择唯一记录的理想工具。
  • 灵活性: mergeFunction可以根据不同的业务需求进行调整。例如,如果你想保留薪资最高的记录,可以将比较逻辑改为existingEmployee.getSalary() > newEmployee.getSalary() ? existingEmployee : newEmployee。
  • 键的生成: 确保键的生成方式能够准确地识别你想要分组的唯一实体。对于多字段组合键,字符串拼接是一种简单的方法,但对于复杂对象或性能敏感的场景,自定义键对象并正确实现equals()和hashCode()会是更好的选择。
  • 替代方案: 另一种实现方式是先使用Collectors.groupingBy按多字段分组,得到Map>,然后再对每个List进行排序和选择。但对于本例这种直接选择一个“最佳”记录的场景,Collectors.toMap通常更简洁高效。

通过本教程,您应该已经掌握了如何使用Java Stream API结合Collectors.toMap,根据对象的多个字段进行分组,并从每个分组中筛选出符合特定条件的唯一记录。这种模式在数据处理和聚合中非常常见且实用。

相关专题

更多
java
java

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

832

2023.06.15

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

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

738

2023.07.05

java自学难吗
java自学难吗

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

734

2023.07.31

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

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

397

2023.08.01

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

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

398

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

Golang gRPC 服务开发与Protobuf实战
Golang gRPC 服务开发与Protobuf实战

本专题系统讲解 Golang 在 gRPC 服务开发中的完整实践,涵盖 Protobuf 定义与代码生成、gRPC 服务端与客户端实现、流式 RPC(Unary/Server/Client/Bidirectional)、错误处理、拦截器、中间件以及与 HTTP/REST 的对接方案。通过实际案例,帮助学习者掌握 使用 Go 构建高性能、强类型、可扩展的 RPC 服务体系,适用于微服务与内部系统通信场景。

8

2026.01.15

热门下载

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

精品课程

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

共23课时 | 2.5万人学习

C# 教程
C# 教程

共94课时 | 6.8万人学习

Java 教程
Java 教程

共578课时 | 46.2万人学习

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

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