讲师中心 微信公众号

工具推荐

视频效率加速
Laper
Laper

剧本策划到成片协作

立刻MV
立刻MV

低成本歌曲 MV、视频生成

Seko
Seko

AI短剧一键生成成片

updream

一键生成高质量推广成片

使用Java Stream高效处理列表去重:按ID保留最新记录

云晨小哥_2318

云晨小哥_2318

发布时间:2025-08-05 10:46:21

|

252人浏览过

|

来源于php中文网

原创

使用java stream高效处理列表去重:按id保留最新记录

本教程详细介绍了如何利用Java Stream API,特别是Collectors.toMap的三参数重载方法,对包含重复ID的对象列表进行去重操作。通过结合Function.identity()和BinaryOperator.maxBy与自定义比较器,可以优雅地解决在ID重复时保留具有最新时间戳记录的需求,最终将处理后的唯一对象集合转换为列表,并可选择进行排序。

在日常的Java开发中,我们经常会遇到需要处理包含重复数据的列表。一个常见的场景是,列表中存在多个对象拥有相同的唯一标识符(如ID),但我们希望根据某个时间字段(如创建时间、更新时间)来决定保留哪一个记录,通常是保留最新的一条。本文将深入探讨如何使用Java Stream API以一种简洁高效的方式实现这一目标。

问题场景与解决方案概述

假设我们有一个Student对象列表,每个Student对象包含一个id和一个startDatetime。我们的目标是:如果存在多个Student对象拥有相同的id,则只保留其中startDatetime最新的那一个。

Java Stream API为我们提供了强大的工具来处理集合数据。解决此问题的核心在于使用Collectors.toMap的重载版本,它允许我们定义一个合并函数来处理键冲突。

定义数据模型

首先,我们需要一个表示学生的数据模型。为了演示,我们定义一个简单的Student类:

AI舌诊面诊问诊医疗系统+诊疗商城系统源码
AI舌诊面诊问诊医疗系统+诊疗商城系统源码

AI 舌诊面诊问诊医疗系统 + 诊疗商城系统源码是一套结合 AI 辅助诊断与商品转化的健康类程序,基于浩创云、ThinkPHP(PHP)开发,支持微信服务号与 H5 端,适合做中医舌诊面诊问答、健康建议推送以及配套诊疗商品的运营。

下载

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

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是解决我们问题的关键。

  1. keyMapper: 定义如何从流中的元素提取键。在这里,我们将使用Student::getId作为键,因为我们希望根据id进行去重。
  2. valueMapper: 定义如何从流中的元素提取值。由于我们希望保留整个Student对象,所以这里可以使用Function.identity(),它表示将流中的元素本身作为值。
  3. 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<Student> 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<Student> 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);
    }
}

代码解析

  1. students.stream(): 创建一个Student对象的流。
  2. .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对象保留下来。
  3. .values(): Collectors.toMap的结果是一个Map。.values()方法返回Map中所有值的集合(Collection),这些值就是我们去重后保留的Student对象。
  4. .stream(): 将Collection再次转换为一个流,以便后续操作。
  5. .sorted(Comparator.comparing(Student::getStartDatetime)): 这一步是可选的,但通常为了输出结果的可预测性,我们会对最终的列表进行排序。这里我们再次按照startDatetime进行升序排序。
  6. .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集合数据。

热门AI工具

更多
WorkBuddy

一款AI办公效率工具,主要用于腾讯云推出的AI原生桌面智能体工作台,适合需要提升相关任务效率的用户。

豆包大模型

豆包大模型是一款由字节跳动推出的企业级大语言模型服务平台。

咔片AIPPT

一款在线AI演示文稿制作工具,可根据主题和内容需求辅助生成PPT结构与页面,提高演示材料制作效率。

PixTV
PixTV Hot

PixTV是一款面向AIGC内容创作的AI视频生成工具。

SkildArt
SkildArt Hot

SkildArt是一款AI文本写作工具,一站式 AI 视觉创作平台。

DeepSeek

DeepSeek是一款面向对话、写作、编程和推理场景的AI大模型工具。

切问学术

切问学术是一款AI论文写作工具,复旦大学NLP团队推出的AI学术智能体。

VibeKnow
VibeKnow Hot

一款AI视频创作工具,主要用于全球首个AI知识视频创作平台,文档、文章、网页,一键生成视频,适合需要提升相关任务效率的用户。

讯飞绘文

讯飞绘文是一款由科大讯飞推出的一站式 AIGC 内容运营平台。

相关专题

更多
string转int
string转int

在编程中,我们经常会遇到需要将字符串(str)转换为整数(int)的情况。这可能是因为我们需要对字符串进行数值计算,或者需要将用户输入的字符串转换为整数进行处理。php中文网给大家带来了相关的教程以及文章,欢迎大家前来学习阅读。

7579

2023.08.02

mysql标识符无效错误怎么解决
mysql标识符无效错误怎么解决

mysql标识符无效错误的解决办法:1、检查标识符是否被其他表或数据库使用;2、检查标识符是否包含特殊字符;3、使用引号包裹标识符;4、使用反引号包裹标识符;5、检查MySQL的配置文件等等。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

18240

2023.12.04

Python标识符有哪些
Python标识符有哪些

Python标识符有变量标识符、函数标识符、类标识符、模块标识符、下划线开头的标识符、双下划线开头、双下划线结尾的标识符、整型标识符、浮点型标识符等等。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

1283

2024.02.23

java标识符合集
java标识符合集

本专题整合了java标识符相关内容,想了解更多详细内容,请阅读下面的文章。

9750

2025.06.11

c++标识符介绍
c++标识符介绍

本专题整合了c++标识符相关内容,阅读专题下面的文章了解更多详细内容。

3882

2025.08.07

线程和进程的区别
线程和进程的区别

线程和进程的区别:线程是进程的一部分,用于实现并发和并行操作,而线程共享进程的资源,通信更方便快捷,切换开销较小。本专题为大家提供线程和进程区别相关的各种文章、以及下载和课程。

4898

2023.08.10

golang map内存释放
golang map内存释放

本专题整合了golang map内存相关教程,阅读专题下面的文章了解更多相关内容。

750

2025.09.05

golang map相关教程
golang map相关教程

本专题整合了golang map相关教程,阅读专题下面的文章了解更多详细内容。

343

2025.11.16

Go-Zero API开发使用教程
Go-Zero API开发使用教程

本专题聚焦Go-Zero的API开发,拆解.api描述文件的语法结构、路由与分组定义、参数绑定与校验规则,说明统一响应体、JWT鉴权、跨域与接口文档生成的落地方式,并给出网关聚合与接口测试的实践建议。

100

2026.11.12

热门下载

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

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
Go-Zero goctl安装与使用手册
Go-Zero goctl安装与使用手册

共0课时 | 0人学习

DBeaver生态工具dbvr官方手册
DBeaver生态工具dbvr官方手册

共0课时 | 0人学习

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

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn