
在数据处理中,我们经常遇到需要从列表中移除重复项的场景。然而,简单的去重往往不能满足所有需求。例如,当列表中存在多个具有相同标识符(id)的对象时,我们可能需要根据某个特定属性(如时间戳)来决定保留哪一个。本教程将聚焦于一个典型场景:给定一个包含 student 对象的列表,每个 student 对象都有一个 id 和一个 startdatetime。如果存在多个 student 对象具有相同的 id,我们希望只保留其中 startdatetime 最新的那个。
考虑以下 Student 类定义及其示例数据:
package org.example;
import java.time.LocalDateTime;
import java.util.Objects; // 导入 Objects 类
public class Student {
private String id;
private LocalDateTime startDatetime;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public LocalDateTime getStartDatetime() {
return startDatetime;
}
public void setStartDatetime(LocalDateTime startDatetime) {
this.startDatetime = startDatetime;
}
public Student(String id, LocalDateTime startDatetime) {
this.id = id;
this.startDatetime = startDatetime;
}
@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);
}
}初始数据示例如下:
List<Student> students = List.of(
new Student("1", LocalDateTime.now()),
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))
);我们期望的结果是:对于ID为"1"的学生,保留 LocalDateTime.now() 对应的记录;对于ID为"2"的学生,保留其唯一的记录。最终列表应只包含两条记录。
Java Stream API 提供了强大而灵活的 Collectors 工具类,其中 Collectors.toMap 的三参数版本是解决此类问题的关键。其方法签名通常为:
立即学习“Java免费学习笔记(深入)”;
public static <T, K, U> Collector<T, ?, Map<K, U>> toMap(
Function<? super T, ? extends K> keyMapper,
Function<? super T, ? extends U> valueMapper,
BinaryOperator<U> mergeFunction
)这个方法接受三个参数:
对于我们的 Student 对象,我们希望根据 id 进行去重。因此,keyMapper 应该是一个从 Student 对象中获取其 id 的函数引用:Student::getId。
我们希望在去重后保留完整的 Student 对象,而不是其某个属性。因此,valueMapper 应该简单地返回原始 Student 对象本身。这可以通过 Function.identity() 实现。
这是最核心的部分。当 Collectors.toMap 遇到具有相同键(id)的多个 Student 对象时,mergeFunction 会被调用来决定保留哪一个。我们的目标是保留 startDatetime 最新的那个。
我们可以使用 BinaryOperator.maxBy 结合 Comparator.comparing 来实现这一逻辑:
因此,mergeFunction 的完整表达式为:BinaryOperator.maxBy(Comparator.comparing(Student::getStartDatetime))。
将上述概念整合到完整的 Java 代码中:
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.function.BinaryOperator;
import java.util.function.Function;
import java.util.stream.Collectors;
public class Main {
// Student 类定义(与上面保持一致)
public static class Student {
private String id;
private LocalDateTime startDatetime;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public LocalDateTime getStartDatetime() {
return startDatetime;
}
public void setStartDatetime(LocalDateTime startDatetime) {
this.startDatetime = startDatetime;
}
public Student(String id, LocalDateTime startDatetime) {
this.id = id;
this.startDatetime = startDatetime;
}
@Override
public String toString() {
return "Student{id='" + id + "', startDatetime=" + startDatetime + '}';
}
}
public static void main(String[] args) {
// 原始学生列表
List<Student> students = new ArrayList<>() {{
add(new Student("1", LocalDateTime.now())); // 最新的ID为1的记录
add(new Student("1", LocalDateTime.of(2000, 2, 1, 1, 1)));
add(new Student("1", LocalDateTime.of(1990, 2, 1, 1, 1)));
add(new Student("2", LocalDateTime.of(1990, 2, 1, 1, 1)));
add(new Student("3", LocalDateTime.of(2020, 1, 1, 0, 0))); // 新增一个不重复的记录
add(new Student("3", LocalDateTime.of(2019, 1, 1, 0, 0))); // 较旧的ID为3的记录
}};
System.out.println("原始学生列表:");
students.forEach(System.out::println);
System.out.println("--------------------");
// 使用 Stream API 去重并保留最新记录
List<Student> uniqueStudents = students.stream()
.collect(Collectors.toMap(
Student::getId, // keyMapper: 以ID作为键
Function.identity(), // valueMapper: 保留原始Student对象作为值
BinaryOperator.maxBy(Comparator.comparing(Student::getStartDatetime)) // mergeFunction: 冲突时保留startDatetime最新的
))
.values() // 获取Map中所有的值(去重后的Student对象)
.stream() // 将值集合转换为新的Stream
.sorted(Comparator.comparing(Student::getStartDatetime)) // 可选:根据startDatetime排序结果
.toList(); // Java 16+ 新特性,等价于 .collect(Collectors.toList())
System.out.println("去重并保留最新记录后的学生列表:");
uniqueStudents.forEach(System.out::println);
}
}运行结果示例(LocalDateTime.now() 会根据运行时间变化):
原始学生列表:
Student{id='1', startDatetime=2023-10-27T10:30:45.123456}
Student{id='1', startDatetime=2000-02-01T01:01}
Student{id='1', startDatetime=1990-02-01T01:01}
Student{id='2', startDatetime=1990-02-01T01:01}
Student{id='3', startDatetime=2020-01-01T00:00}
Student{id='3', startDatetime=2019-01-01T00:00}
--------------------
去重并保留最新记录后的学生列表:
Student{id='2', startDatetime=1990-02-01T01:01}
Student{id='1', startDatetime=2023-10-27T10:30:45.123456} // 此处的日期时间会是运行时的当前时间
Student{id='3', startDatetime=2020-01-01T00:00}可以看到,ID为"1"和"3"的重复记录已被成功去重,并保留了 startDatetime 最新的那一条。最终列表也根据 startDatetime 进行了排序。
在上述代码中,collect(Collectors.toMap(...)) 的结果是一个 Map<String, Student>。我们需要的是一个 List<Student>,因此我们通过 Map.values() 获取到 Map 中所有去重后的 Student 对象集合,然后将其转换为一个新的 Stream,并最终收集为 List。
通过本教程,我们深入探讨了如何利用 Java Stream API 中的 Collectors.toMap 的三参数版本,结合 Function.identity() 和 BinaryOperator.maxBy(Comparator.comparing(...)),优雅且高效地解决列表中对象去重并保留最新记录的问题。这种声明式编程风格不仅提升了代码的简洁性和可读性,也充分展现了 Java Stream 在处理复杂集合操作时的强大能力。掌握这一模式,将有助于您在日常开发中编写出更加健壮和现代的 Java 代码。
以上就是Java Stream 进阶:优雅地移除重复对象并保留最新记录的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号