Java多字段分组取最新记录技巧
时间:2025-09-13 13:36:45 300浏览 收藏
哈喽!大家好,很高兴又见面了,我是golang学习网的一名作者,今天由我给大家带来一篇《Java多字段分组筛选最新记录方法》,本文主要会讲到等等知识点,希望大家一起学习进步,也欢迎大家关注、点赞、收藏、转发! 下面就一起来看看吧!
业务场景概述
在实际开发中,我们经常会遇到需要处理包含重复数据的列表,但这些重复数据在某些关键字段上是唯一的。例如,一个员工列表可能包含同一个员工(通过姓氏和名字确定)在不同时间点的薪资记录。我们的目标是,对于每个唯一的员工组合(例如,相同的姓氏和名字),只保留其最新的一条记录。
假设我们有以下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 ListgetSampleEmployees() { 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; } }
我们的目标是得到以下结果:
- "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的重载方法,该方法允许我们定义一个合并函数来处理键冲突。
核心思路
- 生成唯一的键: 对于每个Employee对象,我们需要一个唯一的键来表示一个特定的员工。在这个例子中,firstName和lastName的组合构成了这个键。
- 处理键冲突: 当两个或多个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) { Listemployees = 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); } }
代码解析
- employees.stream(): 创建一个Employee对象的流。
- .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记录。
- keyMapper (employee -> employee.getFirstName() + employee.getLastName()): 这个函数负责从每个Employee对象中提取出用于作为Map键的部分。在这里,我们将firstName和lastName字符串拼接起来,形成一个唯一的员工标识符。
- .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多字段分组取最新记录技巧》文章吧,也可关注golang学习网公众号了解相关技术文章。
相关阅读
更多>
-
501 收藏
-
501 收藏
-
501 收藏
-
501 收藏
-
501 收藏
最新阅读
更多>
-
179 收藏
-
122 收藏
-
449 收藏
-
347 收藏
-
241 收藏
-
160 收藏
-
291 收藏
-
439 收藏
-
138 收藏
-
310 收藏
-
202 收藏
-
445 收藏
课程推荐
更多>
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 立即学习 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 立即学习 514次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 立即学习 499次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 立即学习 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 立即学习 484次学习