JavaStream保留最新ID去重方法
本文深入探讨了如何利用Java Stream API,特别是`Collectors.toMap`的三参数重载方法,实现对Java对象列表中重复ID数据的去重,并保留最新记录。针对日常开发中常见的,需要根据时间戳或其他字段保留唯一ID最新记录的需求,文章详细介绍了通过`Function.identity()`结合`BinaryOperator.maxBy`与自定义比较器,优雅地解决键冲突问题。通过示例代码,展示了如何将包含重复ID的`Student`对象列表,去重并按时间排序,最终转换为一个包含唯一对象且排序后的列表。掌握此方法,能有效提高Java集合数据处理效率,简化代码,提升可读性,尤其适用于处理大数据量时,保证数据准确性并优化性能。

在日常的Java开发中,我们经常会遇到需要处理包含重复数据的列表。一个常见的场景是,列表中存在多个对象拥有相同的唯一标识符(如ID),但我们希望根据某个时间字段(如创建时间、更新时间)来决定保留哪一个记录,通常是保留最新的一条。本文将深入探讨如何使用Java Stream API以一种简洁高效的方式实现这一目标。
问题场景与解决方案概述
假设我们有一个Student对象列表,每个Student对象包含一个id和一个startDatetime。我们的目标是:如果存在多个Student对象拥有相同的id,则只保留其中startDatetime最新的那一个。
Java Stream API为我们提供了强大的工具来处理集合数据。解决此问题的核心在于使用Collectors.toMap的重载版本,它允许我们定义一个合并函数来处理键冲突。
定义数据模型
首先,我们需要一个表示学生的数据模型。为了演示,我们定义一个简单的Student类:
import java.time.LocalDateTime;
import java.util.Objects; // 引入Objects用于hashCode和equals
public class Student {
private String id;
private LocalDateTime startDatetime;
public Student(String id, LocalDateTime startDatetime) {
this.id = id;
this.startDatetime = startDatetime;
}
public String getId() {
return id;
}
public LocalDateTime getStartDatetime() {
return startDatetime;
}
// 为了更好的演示和调试,建议重写toString方法
@Override
public String toString() {
return "Student{" +
"id='" + id + '\'' +
", startDatetime=" + startDatetime +
'}';
}
// 重写equals和hashCode方法,虽然在此特定场景不直接用于去重,但良好的实践
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Student student = (Student) o;
return Objects.equals(id, student.id) && Objects.equals(startDatetime, student.startDatetime);
}
@Override
public int hashCode() {
return Objects.hash(id, startDatetime);
}
}使用 Collectors.toMap 进行去重
Collectors.toMap有多个重载方法,其中一个接受三个参数:keyMapper、valueMapper和mergeFunction。这个mergeFunction是解决我们问题的关键。
- keyMapper: 定义如何从流中的元素提取键。在这里,我们将使用Student::getId作为键,因为我们希望根据id进行去重。
- valueMapper: 定义如何从流中的元素提取值。由于我们希望保留整个Student对象,所以这里可以使用Function.identity(),它表示将流中的元素本身作为值。
- mergeFunction: 这是处理键冲突(即当两个或更多元素生成相同的键时)的函数。它的签名是BinaryOperator
,接收两个相同类型的参数并返回一个相同类型的结果。我们的目标是保留startDatetime最新的学生对象。
为了实现mergeFunction,我们可以使用BinaryOperator.maxBy方法,它接受一个Comparator作为参数,并返回一个BinaryOperator,该操作符会根据比较器选择两个元素中“最大”的一个。结合Comparator.comparing,我们可以轻松地比较Student对象的startDatetime。
示例代码
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.function.BinaryOperator;
import java.util.function.Function;
import java.util.stream.Collectors;
public class StudentDeduplicator {
public static void main(String[] args) {
// 示例数据
List students = new ArrayList<>(List.of(
new Student("1", LocalDateTime.now()), // 最新的id为1的学生
new Student("1", LocalDateTime.of(2000, 2, 1, 1, 1)),
new Student("1", LocalDateTime.of(1990, 2, 1, 1, 1)),
new Student("2", LocalDateTime.of(1990, 2, 1, 1, 1)),
new Student("3", LocalDateTime.of(2020, 5, 10, 10, 0)),
new Student("3", LocalDateTime.of(2021, 1, 1, 0, 0)) // 最新的id为3的学生
));
System.out.println("原始学生列表:");
students.forEach(System.out::println);
System.out.println("--------------------");
// 使用Stream进行去重,保留最新记录
List uniqueStudents = students.stream()
.collect(Collectors.toMap(
Student::getId, // KeyMapper: 使用Student的ID作为Map的键
Function.identity(), // ValueMapper: 将整个Student对象作为Map的值
BinaryOperator.maxBy(Comparator.comparing(Student::getStartDatetime)) // MergeFunction: 当ID冲突时,保留startDatetime最大的那个
))
.values() // 获取Map中所有的值(即去重后的Student对象)
.stream() // 将值集合转换为新的Stream
// 结果通常需要按某个顺序排列,例如按startDatetime排序
.sorted(Comparator.comparing(Student::getStartDatetime))
.toList(); // Java 16+ 使用.toList(),Java 8-15使用.collect(Collectors.toList())
System.out.println("去重并排序后的学生列表:");
uniqueStudents.forEach(System.out::println);
}
} 代码解析
- students.stream(): 创建一个Student对象的流。
- .collect(Collectors.toMap(...)): 这是核心操作。
- Student::getId: 定义了Map的键,即学生的ID。
- Function.identity(): 定义了Map的值,即Student对象本身。
- BinaryOperator.maxBy(Comparator.comparing(Student::getStartDatetime)): 这是合并函数。当遇到重复的ID时,maxBy会使用提供的Comparator来比较两个Student对象。Comparator.comparing(Student::getStartDatetime)创建了一个比较器,它根据startDatetime字段进行比较。maxBy会选择startDatetime“更大”(即更晚)的那个Student对象保留下来。
- .values(): Collectors.toMap的结果是一个Map
。.values()方法返回Map中所有值的集合(Collection ),这些值就是我们去重后保留的Student对象。 - .stream(): 将Collection
再次转换为一个流,以便后续操作。 - .sorted(Comparator.comparing(Student::getStartDatetime)): 这一步是可选的,但通常为了输出结果的可预测性,我们会对最终的列表进行排序。这里我们再次按照startDatetime进行升序排序。
- .toList(): 将流中的元素收集到一个不可变的List中。对于Java 8到Java 15,需要使用Collectors.toList()。
注意事项
- Function.identity(): 这个静态方法返回一个简单的函数,它总是返回其输入参数。在这里,它确保整个Student对象被用作Map的值。
- BinaryOperator.maxBy vs BinaryOperator.minBy: 如果你的需求是保留startDatetime最“旧”的记录,那么应该使用BinaryOperator.minBy。
- Java 版本兼容性:
- List.of(...)用于创建不可变列表(Java 9+)。
- .toList()是Java 16+的新特性,用于将Stream收集为不可变列表。在Java 8到Java 15中,你需要使用collect(Collectors.toList())。
- 性能考量: 这种方法涉及到构建一个中间的Map。对于非常大的数据集,这会占用额外的内存。然而,对于大多数常见场景,这种方法既简洁又高效。
- 线程安全性: Stream操作本身是无状态的,但如果Student对象是可变的,并且在流操作之外被修改,可能会导致意外行为。在这种场景下,Student对象通常被视为不可变或在流处理过程中不被修改。
总结
通过巧妙地运用Java Stream API中的Collectors.toMap和BinaryOperator.maxBy,我们可以以声明式且易于理解的方式解决列表去重并保留最新记录的问题。这种模式不仅适用于时间戳,也可以应用于任何需要根据某个属性进行比较并保留“最佳”记录的场景,极大地提高了代码的可读性和简洁性。掌握这种Stream操作模式,将有助于你更高效地处理Java集合数据。
本篇关于《JavaStream保留最新ID去重方法》的介绍就到此结束啦,但是学无止境,想要了解学习更多关于文章的相关知识,请关注golang学习网公众号!
Python数据分析:Numpy高效计算技巧
- 上一篇
- Python数据分析:Numpy高效计算技巧
- 下一篇
- 在线观看完整PPT设计参考
-
- 文章 · java教程 | 5小时前 | Java ·
- Java Arrays.compareUnsigned 怎样按无符号顺序比较字节数组
- 453浏览 收藏
-
- 文章 · java教程 | 8小时前 | Java ·
- Java Flight Recorder 怎样按事件类型导出记录
- 288浏览 收藏
-
- 文章 · java教程 | 8小时前 | java 并发编程 reentrantlock tryLock 公平锁
- Java ReentrantLock 怎样使用公平锁模式
- 247浏览 收藏
-
- 文章 · java教程 | 8小时前 |
- Java HttpResponse.BodyHandlers 怎样流式消费响应
- 220浏览 收藏
-
- 文章 · java教程 | 8小时前 | Java · nio · HTTP客户端 · java httpclient HttpRequest BodyPublisher HTTP请求体 BodyPublishers
- Java HttpRequest 怎样设置请求体发布器
- 386浏览 收藏
-
- 文章 · java教程 | 8小时前 | 文件处理 · Java · 异常处理 · nio · java 文件替换 Files.move ATOMIC_MOVE REPLACE_EXISTING 原子移动
- Java Files.move 怎样选择 ATOMIC_MOVE 与 REPLACE_EXISTING
- 290浏览 收藏
-
- 文章 · java教程 | 8小时前 | Java · nio · 符号链接 路径处理 Java NIO Java Path.normalize toRealPath
- Java Path.normalize 为什么不会检查符号链接
- 497浏览 收藏
-
- 文章 · java教程 | 8小时前 | java 精度 Double bigdecimal ValueOf
- Java BigDecimal 为什么用 double 构造会产生尾数误差
- 162浏览 收藏
-
- 文章 · java教程 | 8小时前 |
- Java Map.computeIfAbsent 为什么不能在映射函数中递归修改
- 450浏览 收藏
-
- 文章 · java教程 | 8小时前 |
- Java Collectors.filtering 怎样在分组收集时过滤值
- 249浏览 收藏
-
- 文章 · java教程 | 8小时前 |
- Java String.splitWithDelimiters 怎样保留文本中的分隔符
- 359浏览 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 485次学习
-
- PubMedQA
- 深入了解PubMedQA生物医学问答数据集,涵盖其核心功能、使用方法及在临床决策、药物研发等场景的应用,助力提升NLP模型性能。
- 724次使用
-
- H2O EvalGPT
- H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
- 733次使用
-
- LMArena
- LMArena是加州大学伯克利分校推出的AI模型匿名评测平台。通过盲测投票机制,用户可对比不同大模型回答并生成实时排行榜,助力开发者优化模型及用户选择最佳AI工具。
- 808次使用
-
- HELM
- 深入了解斯坦福推出的HELM(Holistic Evaluation of Language Models)大模型评测体系。本文解析其核心功能、安装配置步骤及应用场景,涵盖准确性、公平性、鲁棒性等多维度指标,助力开发者全面优化语言模型性能。
- 770次使用
-
- MMBench
- MMBench是由上海人工智能实验室等机构联合推出的多模态基准测试平台,提供细粒度能力评估、大规模数据集及VLMEvalKit工具。本文详细介绍其核心功能、安装使用方法及应用场景,助力开发者全面评估多模态模型性能。
- 609次使用
-
- Java 时间格式兼容多个输入格式的方法
- 2026-10-18 501浏览
-
- Java try-with-resources 多个资源关闭顺序是什么
- 2026-09-10 501浏览
-
- 矩阵主副对角线快速定位技巧
- 2026-05-31 501浏览
-
- Java多态优化流程代码与行为分发改进
- 2026-05-26 501浏览
-
- JVM 类元数据双亲委派链表深度解析
- 2026-05-21 501浏览

