高效利用Java Stream API:多条件过滤、按月分组与聚合求和

高效利用java stream api:多条件过滤、按月分组与聚合求和

本文详细阐述了如何使用Java 8 Stream API处理复杂数据转换,包括对`Person`对象列表进行多条件过滤、根据日期月份和事件类型进行分组,并最终计算每个分组的总数。教程通过构建复合键、链式调用Stream操作,展示了从原始数据到结构化结果的完整流程。

Java Stream API实现数据过滤、分组与聚合

在现代Java应用开发中,数据处理是核心任务之一。Java 8引入的Stream API极大地简化了集合操作,提供了声明式、函数式编程风格来处理数据。本教程将深入探讨如何利用Stream API实现一个常见的数据转换场景:从一个Person对象集合中,根据特定条件过滤数据,然后按事件发生的月份和类型进行分组,并最终统计每个分组的记录总数。

1. 定义数据模型

首先,我们需要定义涉及到的数据模型。Person类代表原始数据,包含人员ID、姓名、事件类型和事件日期等信息。State枚举用于表示事件类型(如JOIN或EXIT)。DTO类是最终输出结果的结构,包含月份、事件类型和总人数。为了实现多条件分组,我们还需要一个复合键类,这里我们使用MonthState。

import java.time.LocalDate;import java.util.List;import java.util.Map;import java.util.Objects;import java.util.Comparator;import java.util.stream.Collectors;// 事件类型枚举public enum State {    JOIN,    EXIT;}// 原始数据模型public class Person {    private String id;    private String name;    private String surname;    private State event; // JOIN, EXIT    private Object value; // 示例中未使用,可根据实际需求调整    private LocalDate eventDate;    public Person(String id, String name, String surname, State event, LocalDate eventDate) {        this.id = id;        this.name = name;        this.surname = surname;        this.event = event;        this.eventDate = eventDate;    }    // Getters    public String getId() { return id; }    public State getEvent() { return event; }    public LocalDate getEventDate() { return eventDate; }    @Override    public String toString() {        return "Person{" + "id='" + id + ''' + ", event=" + event + ", eventDate=" + eventDate + '}';    }}// 最终输出数据模型public class DTO {    private int month;    private State info;    private int totalEmployees;    public DTO(int month, State info, int totalEmployees) {        this.month = month;        this.info = info;        this.totalEmployees = totalEmployees;    }    // Getters    public int getMonth() { return month; }    public State getInfo() { return info; }    public int getTotalEmployees() { return totalEmployees; }    @Override    public String toString() {        return "DTO{" + "month=" + month + ", info=" + info + ", totalEmployees=" + totalEmployees + '}';    }}// 用于分组的复合键(Java 16+ 可使用 record)public record MonthState(int month, State info) {}// 对于Java 8-15,可以使用普通类实现复合键,需要重写equals()和hashCode()/*public class MonthState {    private final int month;    private final State info;    public MonthState(int month, State info) {        this.month = month;        this.info = info;    }    public int getMonth() { return month; }    public State getInfo() { return info; }    @Override    public boolean equals(Object o) {        if (this == o) return true;        if (o == null || getClass() != o.getClass()) return false;        MonthState that = (MonthState) o;        return month == that.month && info == that.info;    }    @Override    public int hashCode() {        return Objects.hash(month, info);    }}*/

2. 构建Stream管道

我们的目标是从一个Map<String, List>类型的数据源(其中键是pId,值是该ID对应的Person对象列表)中,生成一个List。这个过程将涉及以下Stream操作:

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

扁平化(flatMap): 由于数据源是一个Map,其值是List,我们需要将其扁平化为一个单一的Person对象流。过滤(filter): 根据Person对象的事件类型进行过滤,只保留JOIN和EXIT事件。分组与计数(groupingBy和counting): 这是核心步骤。我们将使用Collectors.groupingBy()来根据两个属性(月份和事件类型)进行分组,并使用Collectors.counting()作为下游收集器来统计每个分组的元素数量。映射(map): 将groupingBy操作产生的Map.Entry(键为MonthState,值为Long类型的计数)转换为最终的DTO对象。排序(sorted): 根据月份对最终结果进行排序。收集(toList): 将Stream中的元素收集到一个List中。

下面是实现上述逻辑的Java Stream代码:

public class StreamProcessingExample {    public static void main(String[] args) {        // 示例数据初始化        Map<String, List> personListById = Map.of(            "per1", List.of(new Person("per1", "Alice", "Smith", State.JOIN, LocalDate.of(2022, 1, 10))),            "per2", List.of(new Person("per2", "Bob", "Johnson", State.JOIN, LocalDate.of(2022, 1, 10))),            "per3", List.of(                new Person("per3", "Charlie", "Brown", State.EXIT, LocalDate.of(2022, 1, 10)),                new Person("per3", "Charlie", "Brown", State.EXIT, LocalDate.of(2022, 2, 10))            ),            "per4", List.of(new Person("per4", "David", "Miller", State.JOIN, LocalDate.of(2022, 3, 10)))        );        List result = personListById.values().stream()            // 1. 扁平化:将 Map 的值(List)扁平化为 Person 对象的流            .flatMap(List::stream)            // 2. 过滤:只保留 JOIN 或 EXIT 类型的事件            .filter(per -> per.getEvent() == State.EXIT || per.getEvent() == State.JOIN)            // 3. 分组与计数:            //    - 使用 MonthState 作为复合键,根据事件日期月份和事件类型进行分组            //    - 使用 Collectors.counting() 统计每个分组的元素数量            .collect(Collectors.groupingBy(                p -> new MonthState(p.getEventDate().getMonthValue(), p.getEvent()),                Collectors.counting()            ))            // 4. 将 Map.Entry 流转换为 DTO 对象流            .entrySet().stream()            .map(e -> new DTO(e.getKey().month(), e.getKey().info(), (int) (long) e.getValue()))            // 5. 排序:根据月份对 DTO 列表进行排序            .sorted(Comparator.comparing(DTO::getMonth))            // 6. 收集:将结果收集为 List            .toList(); // 或者 .collect(Collectors.toList());        // 打印结果        result.forEach(System.out::println);        /* 预期输出:        DTO{month=1, info=JOIN, totalEmployees=2}        DTO{month=1, info=EXIT, totalEmployees=1}        DTO{month=2, info=EXIT, totalEmployees=1}        DTO{month=3, info=JOIN, totalEmployees=1}        */    }}

3. 代码解析与注意事项

flatMap(List::stream): personListById.values()返回的是Collection<List>。flatMap操作将每个List转换为一个Person流,然后将所有这些流连接成一个单一的Person流。filter(per -> per.getEvent() == State.EXIT || per.getEvent() == State.JOIN): 这是一个谓词,用于筛选出我们感兴趣的事件类型。Collectors.groupingBy(p -> new MonthState(p.getEventDate().getMonthValue(), p.getEvent()), Collectors.counting()):groupingBy是Stream API中最强大的收集器之一,用于将元素分组到Map中。第一个参数是一个Function,它定义了如何从流中的每个元素提取键。在这里,我们创建一个MonthState实例作为复合键,它结合了月份和事件类型。MonthState类(或record)正确地实现了equals()和hashCode()方法,这对于groupingBy正确工作至关重要。第二个参数是下游收集器(downstream collector),它定义了如何处理每个分组中的元素。Collectors.counting()简单地计算每个分组中的元素数量,返回一个Long类型的值。.entrySet().stream().map(e -> new DTO(e.getKey().month(), e.getKey().info(), (int) (long) e.getValue())):groupingBy的结果是一个Map。我们通过.entrySet().stream()将其转换为一个Map.Entry流。map操作将每个Map.Entry转换为一个DTO对象。注意,e.getKey()返回的是MonthState对象,我们可以通过其getter方法(或record的访问器方法)获取月份和事件类型。e.getValue()返回的是Long类型的计数,需要强制转换为int以匹配DTO的totalEmployees字段。.sorted(Comparator.comparing(DTO::getMonth)): 使用Comparator.comparing()方法根据DTO对象的month属性进行排序,确保输出结果按月份递增。.toList(): 这是Java 16引入的便捷方法,用于将Stream中的所有元素收集到一个不可变的List中。在Java 8-15中,可以使用collect(Collectors.toList())。

4. 总结

通过本教程,我们学习了如何利用Java Stream API的flatMap、filter、groupingBy、counting、map和sorted等操作,高效地处理复杂的数据转换需求。关键在于理解每个Stream操作的作用,并合理地组合它们以构建一个清晰、声明式的数据处理管道。特别是,当需要根据多个属性进行分组时,定义一个合适的复合键(如MonthState record或类)是至关重要的一步。这种函数式编程风格不仅使代码更简洁,也提高了其可读性和可维护性。

以上就是高效利用Java Stream API:多条件过滤、按月分组与聚合求和的详细内容,更多请关注创想鸟其它相关文章!

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 chuangxiangniao@163.com 举报,一经查实,本站将立刻删除。
发布者:程序猿,转转请注明出处:https://www.chuangxiangniao.com/p/15536.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
PC玩家有福了!微软征集Win10游戏体验改进建议:要走心优化
上一篇 2025年11月27日 10:56:43
Via浏览器怎么查看油猴脚本的控制台日志_Via浏览器查看油猴脚本控制台输出的方法
下一篇 2025年11月27日 10:58:45

相关推荐

  • Linux系统信息查看命令整理

    答案:掌握Linux系统需从系统信息、资源使用、性能瓶颈、日志分析和用户权限五方面入手。uname、lscpu、free、df、ip、ss等命令用于查看系统软硬件状态;top、htop、vmstat、iostat、iftop等可诊断CPU、内存、磁盘、网络性能瓶颈;/var/log日志文件结合jou…

    2026年9月20日
    100
  • 灵绘AI如何生成艺术画_灵绘AI艺术画创作的完整流程

    首先启动灵绘AI并选择“艺术创作”模式,确保设备联网;接着在提示词框输入具体场景描述并添加风格关键词;然后调节细节等级至60%-80%、创意强度为7,并选择3:4或16:9比例;点击生成后预览结果,不满意可重新生成最多五次;对局部不满意区域使用“局部重绘”功能修改;最后导出时选择4K高清并保留图层信…

    2026年9月20日
    300
  • 如何在Java中声明常量数组

    声明常量数组需用static final,但final仅保证引用不可变而非内容不可变。1. 基本类型数组可用static final声明,如public static final int[] DAYS_IN_MONTH = {31,28,…};引用不可改,但元素可修改。2. 为实现内容不…

    2026年9月20日
    100
  • 事务(Transaction)处理与并发控制

    事务处理确保操作全部完成或不完成,并发控制防止事务互相干扰。事务处理核心是acid属性:1.原子性,2.一致性,3.隔离性,4.持久性;并发控制方法包括锁和mvcc,优化需考虑事务粒度、隔离级别、锁和mvcc的应用。 事务处理与并发控制是数据库管理系统中至关重要的两个概念,确保数据的一致性和完整性。…

    2026年9月20日
    100
  • Java从文本文件随机读取并打印指定行数内容

    本文旨在指导读者如何使用java程序从文本文件中高效地读取多组固定行数的内容(如诗歌),并随机选择其中一组进行打印。教程将详细介绍如何利用`files.readalllines`、`random`和`list.sublist`等核心api,实现文件的整体读取、随机索引的生成以及特定内容块的提取与输出…

    2026年9月20日
    100
  • edge浏览器”编写”功能(Copilot)怎么帮你润色文案_edge浏览器AI写作助手使用教程

    答案:通过Edge浏览器内置Copilot可实现文案润色。依次开启AI功能、登录账户,在文本框右键选择“使用Copilot改写”,可选正式、简短等语气风格,或通过侧边栏批量处理内容,提升写作效率。 如果您在撰写网页内容或文档时希望获得智能建议和语言优化,可以借助 Microsoft Edge 浏览器…

    2026年9月20日
    100
  • 小米Civi 3前置摄像头像素是多少 小米Civi 3自拍模式优化技巧

    小米Civi 3前置双3200万像素摄像头,配备78°f/2.0光圈主摄和100°超广角镜头,支持AF对焦与AI畸变矫正;通过切换镜头、开启4K拍摄、调节美颜、优化光线及启用EIS防抖,可显著提升自拍与Vlog画质表现。 小米Civi 3的前置摄像头配置在同级别中非常突出,自拍表现力强,配合一些使用…

    2026年9月20日
    100
  • 美篇如何添加背景音乐

    首先通过美篇内置音乐库或本地导入音频添加背景音乐,再调整自动播放与音量设置,确保阅读体验沉浸且不突兀。 如果您在编辑美篇文章时希望增加背景音乐以提升阅读体验,但不清楚具体操作步骤,可以按照以下方法进行设置。背景音乐能够为图文内容增添氛围,使读者获得更沉浸的浏览感受。 本文运行环境:iPad Air,…

    2026年9月20日
    100
  • PHP播放HLS视频流的方法_PHP播放HLS视频流方法

    答案:PHP通过权限控制和文件代理实现HLS流安全分发,前端使用HTML5视频标签和hls.js播放。具体描述:HLS将视频切为.ts片段并用.m3u8索引,PHP后端可校验用户权限、防止盗链,动态输出.m3u8或.ts内容;前端通过video标签加载stream.php?id=1,结合hls.js…

    2026年9月20日
    000
  • Java类的初始化顺序是怎样的 静态代码块和构造代码块先后

    Java类初始化顺序为:父类静态成员→子类静态成员→父类实例成员→父类构造函数→子类实例成员→子类构造函数,静态代码块仅加载时执行一次,构造代码块每次创建对象时执行,且均按书写顺序运行。 Java类的初始化顺序遵循一定的规则,理解这些顺序对掌握对象创建过程非常重要。当一个类被加载并创建实例时,各个代…

    2026年9月20日
    100
  • VSCode有哪些必备的插件?

    EditorConfig for VS Code统一代码风格,2. Prettier自动格式化多语言代码,3. ESLint检查JS/TS错误并集成Prettier,4. GitLens增强Git可视化,5. Path Intellisense补全文件路径,6. 括号高亮提升嵌套识别,7. Auto…

    2026年9月20日
    1000
  • Via浏览器怎么让地址栏显示完整的网址链接_Via浏览器显示完整网址的设置方法

    1、打开Via浏览器设置,进入高级设置中的地址栏选项,开启“显示完整网址”功能;2、在外观设置中关闭简洁模式或极简地址栏,以恢复协议头和路径显示;3、高级用户可借助自定义脚本强制输出完整URL,通过工具箱添加执行脚本实现。 如果您在使用Via浏览器时发现地址栏默认只显示域名而隐藏了完整的网址链接,可…

    2026年9月20日
    000
  • Java从文本文件随机读取多行连续内容的教程

    本教程旨在指导java开发者如何高效地从文本文件中随机读取并打印指定数量(例如5行)的连续内容,尤其适用于处理结构化文本块(如诗歌)。我们将探讨如何避免仅读取文件开头固定行数的局限,通过将文件内容一次性加载到内存并结合随机数生成器来精确选取所需的文本块,从而实现真正的随机性与灵活性。 引言与问题分析…

    2026年9月20日
    200
  • ChatGPT代码会出错吗_AI编程中5个常见错误及解决方法

    AI编程中常见错误包括语法不匹配、逻辑遗漏、API误用、安全漏洞和集成困难,需通过版本明确、测试验证、文档核对、安全扫描和上下文补充等方式解决,结合人工审查与测试才能确保代码质量。 ☞☞☞AI 智能聊天, 问答助手, AI 智能搜索, 免费无限量使用 DeepSeek R1 模型☜☜☜ ChatGP…

    2026年9月20日
    100
  • 或为《刺客信条:起源》相关!巴耶克与艾雅动捕同框

    面容虽被遮挡,但魅力依旧啊!AI 伴学 + 轻薄便携,联想小新平板 12.1,开售啦! 帅是帅,就是比较废指头 FK阿凯 1.4万 0 《刺客信条:起源》的粉丝们近日惊喜地发现,曾为巴耶克与艾雅配音并进行面部捕捉的演员——阿布巴卡尔·萨利姆(Abubakar Salim)与艾莉克斯·威尔顿·里根(A…

    2026年9月20日
    000
  • RBAC(基于角色的权限控制)实现方案

    rbac重要,因为它通过角色管理权限,简化了权限管理,提高了系统安全和管理效率。实现rbac时:1.设计数据库结构,定义用户、角色、权限表及中间表;2.在代码中实现权限检查和角色、权限的动态管理;3.优化性能,防止权限泄露,管理角色膨胀。 在探讨RBAC(基于角色的权限控制)实现方案之前,让我们先来…

    2026年9月20日
    000
  • 腾讯元宝AI便捷体验入口 腾讯元宝网页版在线入口

    腾讯元宝AI便捷体验入口为https://yuanbao.tencent.com,支持网页版、手机APP及微信小程序访问,提供智能问答、文档解析、内容生成等多功能服务。 ☞☞☞AI 智能聊天, 问答助手, AI 智能搜索, 免费无限量使用 DeepSeek R1 模型☜☜☜ 腾讯元宝AI便捷体验入口…

    2026年9月20日
    000
  • Android Activity与Fragment通信及视图访问的最佳实践

    本文旨在解决android开发中activity与fragment之间视图访问和数据通信的常见问题,特别是当使用bottom navigation activity模板时。我们将探讨为何不能直接在activity中访问fragment视图,并详细介绍如何利用fragment的生命周期方法(如`onv…

    2026年9月20日
    100
  • Gemini2.5网页版访问入口_Gemini2.5官方网站下载链接

    Gemini 2.5网页版访问入口为 https://gemini.google.com/app,登录谷歌账号后可使用主交互界面、模型切换、文件上传、历史记录及移动端同步等功能。 ☞☞☞AI 智能聊天, 问答助手, AI 智能搜索, 免费无限量使用 DeepSeek R1 模型☜☜☜ Gemini2…

    2026年9月20日
    000
  • Java Swing:在类中管理 JFrame 实例的两种策略

    本文探讨在 java swing 应用程序中,如何有效地在不同方法中访问和管理 jframe 实例,避免 this 关键字的限制。我们将介绍两种核心策略:将 jframe 作为类成员变量,或使类直接继承 jframe。同时,强调组件应添加到 jframe 的内容面板,而非直接添加到 jframe。 …

    2026年9月20日
    000

发表回复

登录后才能评论
关注微信