讲师中心 微信公众号

工具推荐

视频效率加速
Seko
Seko

AI短剧一键生成成片

Laper
Laper

剧本策划到成片协作

立刻MV
立刻MV

低成本歌曲 MV、视频生成

剧云
剧云

中文短剧剧本制作平台

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

阿墨姑娘_3865

阿墨姑娘_3865

发布时间:2025-08-05 12:32:11

|

655人浏览过

|

来源于php中文网

原创

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

本文详细介绍了如何利用Java Stream API,特别是Collectors.toMap的三参数版本结合BinaryOperator.maxBy,优雅地处理列表中具有重复ID的对象。核心思想是将具有相同ID的对象映射到Map中,并通过自定义合并函数保留具有最新时间戳的记录,最终生成一个去重且保留最新信息的列表。

场景概述

在数据处理中,我们经常会遇到需要从一个对象列表中移除重复项的情况。一个常见的需求是:当多个对象拥有相同的唯一标识(如ID)时,我们希望保留其中某个特定条件的对象,例如拥有最新时间戳(或最大/最小数值)的记录。本教程将以一个具体的示例来演示如何使用Java Stream API高效、简洁地实现这一目标。

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

核心解决方案:Collectors.toMap与合并函数

Java Stream API提供了一个强大的工具——Collectors.toMap,它有多个重载版本。为了解决我们的问题,我们将使用其三参数版本: Collectors.toMap(keyMapper, valueMapper, mergeFunction)

  • keyMapper: 用于从流中的每个元素提取Map的键。在本例中,即Student::getId。
  • valueMapper: 用于从流中的每个元素提取Map的值。由于我们想保留整个Student对象作为值,可以使用Function.identity()。
  • mergeFunction: 这是解决重复冲突的关键。当多个元素映射到同一个键时,此函数将被调用来决定保留哪个值。

为了保留具有最新startDatetime的Student对象,我们需要一个合并函数,它能在两个Student对象中选择startDatetime更大的那个。BinaryOperator.maxBy()正好可以满足这个需求,它接收一个Comparator作为参数,并返回一个BinaryOperator,该操作符会根据Comparator选择两个输入中“更大”的一个。

结合Comparator.comparing(Student::getStartDatetime),我们可以构建出BinaryOperator.maxBy(Comparator.comparing(Student::getStartDatetime))作为合并函数。这意味着,当遇到相同的id时,toMap会比较两个Student对象的startDatetime,并保留时间较晚(即更大)的那个。

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

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

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

下载

示例代码与详细解析

首先,定义我们的Student类:

import java.time.LocalDateTime;
import java.util.Objects;

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(),如果Student对象需要作为Set元素或Map的键
    @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);
    }
}

接下来,是去重并保留最新记录的核心逻辑:

import java.time.LocalDateTime;
import java.util.Comparator;
import java.util.List;
import java.util.function.Function;
import java.util.function.BinaryOperator;
import java.util.stream.Collectors;

public class StudentDeduplicator {

    public static void main(String[] args) {
        // 示例数据
        List<Student> students = List.of(
            new Student("1", LocalDateTime.of(2022, 11, 1, 14, 0)), // 最新
            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(2023, 1, 1, 10, 0)), // 最新
            new Student("3", LocalDateTime.of(2022, 1, 1, 10, 0))  // 较旧
        );

        System.out.println("原始学生列表:");
        students.forEach(System.out::println);

        // 使用Stream API去重并保留最新记录
        List<Student> uniqueStudents = students.stream()
            .collect(Collectors.toMap(
                Student::getId,                                 // keyMapper: 以ID作为Map的键
                Function.identity(),                            // valueMapper: 整个Student对象作为Map的值
                BinaryOperator.maxBy(Comparator.comparing(Student::getStartDatetime)) // mergeFunction: 当ID重复时,保留startDatetime更大的那个
            ))
            .values()                                           // 获取Map中所有去重后的Student对象
            .stream()                                           // 转换为新的Stream
            .sorted(Comparator.comparing(Student::getId))       // 可选:按ID排序,使结果有序
            .collect(Collectors.toList());                      // 收集为List (Java 16+ 可用 .toList())

        System.out.println("\n去重并保留最新记录后的学生列表:");
        uniqueStudents.forEach(System.out::println);
    }
}

输出结果:

原始学生列表:
Student{id='1', startDatetime=2022-11-01T14:00}
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=2023-01-01T10:00}
Student{id='3', startDatetime=2022-01-01T10:00}

去重并保留最新记录后的学生列表:
Student{id='1', startDatetime=2022-11-01T14:00}
Student{id='2', startDatetime=1990-02-01T01:01}
Student{id='3', startDatetime=2023-01-01T10:00}

从输出可以看出,对于ID为"1"的三个学生,只保留了startDatetime为2022-11-01T14:00的那个。对于ID为"3"的两个学生,保留了startDatetime为2023-01-01T10:00的那个。ID为"2"的学生由于唯一,被直接保留。

注意事项

  1. mergeFunction的选择:
    • 如果需要保留最旧的记录,应使用BinaryOperator.minBy(Comparator.comparing(Student::getStartDatetime))。
    • 如果需要根据其他属性(如数字大小)进行筛选,只需修改Comparator.comparing()的参数即可。
  2. 结果集的顺序: Collectors.toMap在内部使用HashMap(默认情况下),因此其结果Map的values()集合的顺序是不确定的。如果最终的List需要保持特定的顺序(例如按ID排序或按时间排序),则需要像示例中那样在values().stream()之后显式地添加.sorted()操作。
  3. 内存消耗: toMap操作会创建一个中间的Map来存储去重后的元素。对于非常大的数据集,这可能会占用较多的内存。但在大多数常见场景下,这种方法是高效且可接受的。
  4. Java版本兼容性: 在Java 16及更高版本中,可以使用.toList()作为终端操作来收集Stream元素到不可变列表。对于早期版本(如Java 8),应使用Collectors.toList()。
  5. Function.identity(): 这个静态方法返回一个函数,该函数总是返回其输入参数。在这里,它确保Map的值就是流中当前的Student对象本身。

总结

通过本教程,我们学习了如何巧妙地运用Java Stream API中的Collectors.toMap结合BinaryOperator.maxBy(或minBy)来解决列表去重并保留特定条件记录的问题。这种方法不仅代码简洁、可读性强,而且在处理集合数据时表现出良好的性能。掌握这种模式对于编写高效且富有表现力的Java代码至关重要。

热门AI工具

更多
超级简历WonderCV

一款AI办公效率工具,主要用于免费求职简历模版下载制作,应届生职场人必备简历制作神器,适合需要提升相关任务效率的用户。

Laper
Laper Hot

Laper是专为编剧、导演和制片人推出的 AI 原生剧本创作工具。

Flowith
Flowith Hot

Flowith是一款基于画布和智能体工作流的 AI 协作工作空间。

豆包大模型

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

Loomy
Loomy Hot

一款AI工具,主要用于科大讯飞发布的桌面级 AI 助理,比 OpenClaw 更易用、更安全!,适合需要提升相关任务效率的用户。

DeepSeek

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

WorkBuddy

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

蛙蛙写作

一款AI论文写作工具,主要用于超级AI智能写作助手,适合需要提升相关任务效率的用户。

墨刀AI
墨刀AI Hot

一款AI图像与设计工具,主要用于产品经理的专属智能体,适合需要提升相关任务效率的用户。

相关专题

更多
golang map内存释放
golang map内存释放

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

750

2025.09.05

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

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

343

2025.11.16

golang map原理
golang map原理

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

553

2025.11.17

java判断map相关教程
java判断map相关教程

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

323

2025.11.27

function是什么
function是什么

function是函数的意思,是一段具有特定功能的可重复使用的代码块,是程序的基本组成单元之一,可以接受输入参数,执行特定的操作,并返回结果。本专题为大家提供function是什么的相关的文章、下载、课程内容,供大家免费下载体验。

3740

2023.08.04

js函数function用法
js函数function用法

js函数function用法有:1、声明函数;2、调用函数;3、函数参数;4、函数返回值;5、匿名函数;6、函数作为参数;7、函数作用域;8、递归函数。本专题提供js函数function用法的相关文章内容,大家可以免费阅读。

614

2023.10.07

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

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

100

2026.11.12

Go-Zero安装与goctl环境配置教程
Go-Zero安装与goctl环境配置教程

本专题围绕Go-Zero的安装与工具链配置展开,讲清Go版本要求、goctl安装方式、代理镜像、protoc依赖与环境变量设置,说明多版本共存、升级回退与CI流水线安装的做法,并整理安装阶段的常见报错与验证步骤。

60

2026.11.12

Go-Zero零基础入门教程
Go-Zero零基础入门教程

本专题整理Go-Zero的入门知识,从框架定位、环境准备到跑通第一个API服务,讲清目录分层、配置加载、日志与中间件的基本用法,并对比Gin、Kratos的差异,帮助Go开发者建立整体认知,判断它是否适合当前项目。

60

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