0

0

Java Stream高级聚合:实现按月统计总和与记录计数

碧海醫心

碧海醫心

发布时间:2025-10-17 12:23:08

|

229人浏览过

|

来源于php中文网

原创

Java Stream高级聚合:实现按月统计总和与记录计数

本教程详细讲解如何利用java stream api,结合`collectors.groupingby`与`collectors.reducing`进行复杂数据聚合。我们将通过自定义度量模型,实现按月分组统计特定数值的总和,并计算每个月内满足条件的记录数量,同时探讨如何扩展以实现独立实体的去重计数。

在现代Java应用中,数据处理和聚合是常见的任务。Java Stream API提供了强大而灵活的工具来处理集合数据,但当需要同时进行多条件分组、求和、计数等复杂聚合时,简单的counting()或summingInt()可能无法满足需求。本文将深入探讨如何利用Collectors.groupingBy结合Collectors.reducing,通过自定义聚合逻辑来解决这类问题。

1. 核心数据模型定义

首先,我们定义处理数据所需的基本模型类。为了简洁,这里使用Java 14+ 引入的 record 类型。

import java.time.LocalDate;
import java.math.BigDecimal; // 考虑到数值精度,使用BigDecimal

// 事件状态枚举
enum Statement {
    STATUS1, STATUS2, STATUS3, STATUS4 // 示例中包含STATUS4
}

// 原始Person数据模型
record Person(String id,
              Statement event,
              LocalDate eventDate,
              int value) {} // 简化value为int类型

// 最终聚合结果的数据传输对象
record DTO(int month,
           BigDecimal totalSum,
           int totalPersons) {}

2. 自定义聚合度量模型 (PersonGroupMetric)

为了在一次Stream操作中同时累积多个聚合值(例如总和与计数),我们需要一个自定义的度量模型。这个模型将封装我们感兴趣的聚合状态,并提供合并这些状态的方法。

record PersonGroupMetric(int count, int sum) {

    // 定义一个空度量,作为reducing操作的初始值
    public static final PersonGroupMetric EMPTY = new PersonGroupMetric(0, 0);

    // 构造函数:将单个Person对象转换为初始度量
    public PersonGroupMetric(Person p) {
        // 每遇到一个Person对象,计数加1,总和累加其value
        this(1, p.value());
    }

    // 合并方法:将两个PersonGroupMetric实例的计数和总和相加
    public PersonGroupMetric add(PersonGroupMetric other) {
        return new PersonGroupMetric(
            this.count + other.count,
            this.sum + other.sum
        );
    }
}

PersonGroupMetric记录了每个组(这里是每个月)的记录数量和值总和。

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

  • EMPTY常量作为reducing操作的“身份元素”,表示一个初始的、不影响聚合结果的度量。
  • PersonGroupMetric(Person p)构造函数负责将Stream中的每个Person对象映射成一个初始的PersonGroupMetric实例。
  • add方法则定义了如何将两个PersonGroupMetric实例合并成一个新的实例,这是reducing操作的核心逻辑。

3. Stream聚合逻辑

现在,我们利用Collectors.groupingBy和Collectors.reducing来实现复杂的聚合。

假设我们有以下示例数据:

import java.util.List;
import java.util.Map;
import static java.util.stream.Collectors.*; // 导入所有Collectors静态方法

public class StreamAggregationExample {
    public static void main(String[] args) {
        var src = List.of(
            new Person("per1", Statement.STATUS1, LocalDate.of(2022, 01, 10), 1),
            new Person("per2", Statement.STATUS2, LocalDate.of(2022, 01, 10), 2),
            new Person("per3", Statement.STATUS3, LocalDate.of(2022, 01, 10), 3),
            new Person("per1", Statement.STATUS4, LocalDate.of(2022, 01, 10), 1), // per1在1月份出现两次
            new Person("per1", Statement.STATUS1, LocalDate.of(2022, 02, 10), 1),
            new Person("per2", Statement.STATUS1, LocalDate.of(2022, 03, 10), 1),
            new Person("per3", Statement.STATUS2, LocalDate.of(2022, 03, 10), 2)
        );

        // 使用groupingBy和reducing进行聚合
        Map aggregatedMetrics = src.stream()
            // 可以选择性地添加过滤条件,例如只处理特定STATUS
            // .filter(p -> p.event() == Statement.STATUS1 || p.event() == Statement.STATUS2)
            .collect(groupingBy(
                p -> p.eventDate().getMonthValue(), // 按月份分组
                reducing(
                    PersonGroupMetric.EMPTY,      // 1. 初始值 (identity)
                    PersonGroupMetric::new,       // 2. 映射器 (mapper): 将Person转换为PersonGroupMetric
                    PersonGroupMetric::add        // 3. 累加器 (accumulator): 合并PersonGroupMetric
                )
            ));

        System.out.println("聚合结果 (Map):");
        aggregatedMetrics.forEach((month, metric) -> 
            System.out.println("Month: " + month + ", Count: " + metric.count() + ", Sum: " + metric.sum()));
    }
}

在这个Stream操作中:

kimi.ai
kimi.ai

Kimi.ai 是月之暗面(Moonshot AI)公司推出的AI智能聊天机器人,能进行智能闲聊、解答问题,提供生活AI助手服务等。

下载
  1. groupingBy(p -> p.eventDate().getMonthValue(), ...):首先根据Person对象的eventDate字段提取月份值,作为分组的键。
  2. reducing(...):这是实现自定义聚合的关键。
    • PersonGroupMetric.EMPTY:reducing操作的起始值,当一个组第一次被处理时,或者当没有元素时,它将作为基础。
    • PersonGroupMetric::new:这是一个映射函数,它将Stream中的每个Person对象转换为一个PersonGroupMetric实例。
    • PersonGroupMetric::add:这是一个累加器(或组合器)函数,它定义了如何将两个PersonGroupMetric实例合并成一个。groupingBy在处理同一个组内的元素时,会反复调用这个方法来累积结果。

4. 结果转换与展示

聚合完成后,我们得到一个Map,其中键是月份,值是对应的聚合度量。为了与我们定义的DTO匹配,我们需要将这个Map转换为List

        // ... (接上文的main方法)

        // 将聚合结果映射到最终的DTO列表
        List finalResult = aggregatedMetrics.entrySet().stream()
            .map(entry -> new DTO(
                entry.getKey(),                           // 月份
                new BigDecimal(entry.getValue().sum()),   // 总和 (转换为BigDecimal)
                entry.getValue().count()                  // 记录数量
            ))
            .sorted((dto1, dto2) -> Integer.compare(dto1.month(), dto2.month())) // 按月份排序
            .collect(toList());

        System.out.println("\n最终结果 (List):");
        finalResult.forEach(System.out::println);
    }
}

输出结果:

聚合结果 (Map):
Month: 1, Count: 4, Sum: 7
Month: 2, Count: 1, Sum: 1
Month: 3, Count: 2, Sum: 3

最终结果 (List):
DTO[month=1, totalSum=7, totalPersons=4]
DTO[month=2, totalSum=1, totalPersons=1]
DTO[month=3, totalSum=3, totalPersons=2]

5. 注意事项与扩展:实现独立人数统计

当前PersonGroupMetric中的count字段统计的是Person记录的出现次数。例如,在1月份,per1出现了两次,因此count为4(per1(STATUS1), per2(STATUS2), per3(STATUS3), per1(STATUS4))。然而,原始需求中“Person Count”可能指的是独立(去重)的人数(即1月份是3人:per1, per2, per3)。

要实现独立人数统计,我们需要修改PersonGroupMetric来存储独立的人员ID。

import java.util.HashSet;
import java.util.Set;

// 修改后的聚合度量模型,包含独立ID集合
record PersonGroupMetricWithDistinct(Set distinctIds, int sum) {

    public static final PersonGroupMetricWithDistinct EMPTY = 
        new PersonGroupMetricWithDistinct(new HashSet<>(), 0);

    public PersonGroupMetricWithDistinct(Person p) {
        this(new HashSet<>(Set.of(p.id())), p.value()); // 初始化时添加当前Person的ID
    }

    public PersonGroupMetricWithDistinct add(PersonGroupMetricWithDistinct other) {
        Set mergedIds = new HashSet<>(this.distinctIds);
        mergedIds.addAll(other.distinctIds); // 合并独立ID集合
        return new PersonGroupMetricWithDistinct(
            mergedIds,
            this.sum + other.sum
        );
    }
}

然后,在Stream聚合逻辑中,使用新的PersonGroupMetricWithDistinct:

        // ... (接上文的main方法,替换PersonGroupMetric为PersonGroupMetricWithDistinct)

        Map aggregatedMetricsDistinct = src.stream()
            .collect(groupingBy(
                p -> p.eventDate().getMonthValue(),
                reducing(
                    PersonGroupMetricWithDistinct.EMPTY,
                    PersonGroupMetricWithDistinct::new,
                    PersonGroupMetricWithDistinct::add
                )
            ));

        System.out.println("\n聚合结果 (Map):");
        aggregatedMetricsDistinct.forEach((month, metric) -> 
            System.out.println("Month: " + month + ", Distinct Person Count: " + metric.distinctIds().size() + ", Sum: " + metric.sum()));

        // 将聚合结果映射到最终的DTO列表 (使用独立人数)
        List finalResultDistinct = aggregatedMetricsDistinct.entrySet().stream()
            .map(entry -> new DTO(
                entry.getKey(),
                new BigDecimal(entry.getValue().sum()),
                entry.getValue().distinctIds().size() // 使用独立ID的数量
            ))
            .sorted((dto1, dto2) -> Integer.compare(dto1.month(), dto2.month()))
            .collect(toList());

        System.out.println("\n最终结果 (List - 独立人数):");
        finalResultDistinct.forEach(System.out::println);

此时的输出将更符合独立人数的统计:

聚合结果 (Map):
Month: 1, Distinct Person Count: 3, Sum: 7
Month: 2, Distinct Person Count: 1, Sum: 1
Month: 3, Distinct Person Count: 2, Sum: 3

最终结果 (List - 独立人数):
DTO[month=1, totalSum=7, totalPersons=3]
DTO[month=2, totalSum=1, totalPersons=1]
DTO[month=3, totalSum=3, totalPersons=2]

总结

通过本教程,我们学习了如何利用Java Stream API中的Collectors.groupingBy与Collectors.reducing,结合自定义的度量模型,实现复杂的数据聚合任务。这种方法不仅能够灵活地进行多条件分组,还能在一次Stream操作中同时计算多个聚合指标,如总和与计数。特别是当需要进行去重计数时,通过在自定义度量模型中引入Set来跟踪独立实体,可以优雅地解决这一挑战。掌握这种高级聚合技巧,将显著提升您在Java数据处理方面的能力。

热门AI工具

更多
DeepSeek
DeepSeek

幻方量化公司旗下的开源大模型平台

豆包大模型
豆包大模型

字节跳动自主研发的一系列大型语言模型

通义千问
通义千问

阿里巴巴推出的全能AI助手

腾讯元宝
腾讯元宝

腾讯混元平台推出的AI助手

文心一言
文心一言

文心一言是百度开发的AI聊天机器人,通过对话可以生成各种形式的内容。

讯飞写作
讯飞写作

基于讯飞星火大模型的AI写作工具,可以快速生成新闻稿件、品宣文案、工作总结、心得体会等各种文文稿

即梦AI
即梦AI

一站式AI创作平台,免费AI图片和视频生成。

ChatGPT
ChatGPT

最最强大的AI聊天机器人程序,ChatGPT不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

相关专题

更多
java基础知识汇总
java基础知识汇总

java基础知识有Java的历史和特点、Java的开发环境、Java的基本数据类型、变量和常量、运算符和表达式、控制语句、数组和字符串等等知识点。想要知道更多关于java基础知识的朋友,请阅读本专题下面的的有关文章,欢迎大家来php中文网学习。

1501

2023.10.24

counta和count的区别
counta和count的区别

Count函数用于计算指定范围内数字的个数,而CountA函数用于计算指定范围内非空单元格的个数。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

198

2023.11.20

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

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

75

2025.09.05

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

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

36

2025.11.16

golang map原理
golang map原理

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

60

2025.11.17

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

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

42

2025.11.27

clawdbot ai使用教程 保姆级clawdbot部署安装手册
clawdbot ai使用教程 保姆级clawdbot部署安装手册

Clawdbot是一个“有灵魂”的AI助手,可以帮用户清空收件箱、发送电子邮件、管理日历、办理航班值机等等,并且可以接入用户常用的任何聊天APP,所有的操作均可通过WhatsApp、Telegram等平台完成,用户只需通过对话,就能操控设备自动执行各类任务。

15

2026.01.29

clawdbot龙虾机器人官网入口 clawdbot ai官方网站地址
clawdbot龙虾机器人官网入口 clawdbot ai官方网站地址

clawdbot龙虾机器人官网入口:https://clawd.bot/,clawdbot ai是一个“有灵魂”的AI助手,可以帮用户清空收件箱、发送电子邮件、管理日历、办理航班值机等等,并且可以接入用户常用的任何聊天APP,所有的操作均可通过WhatsApp、Telegram等平台完成,用户只需通过对话,就能操控设备自动执行各类任务。

12

2026.01.29

Golang 网络安全与加密实战
Golang 网络安全与加密实战

本专题系统讲解 Golang 在网络安全与加密技术中的应用,包括对称加密与非对称加密(AES、RSA)、哈希与数字签名、JWT身份认证、SSL/TLS 安全通信、常见网络攻击防范(如SQL注入、XSS、CSRF)及其防护措施。通过实战案例,帮助学习者掌握 如何使用 Go 语言保障网络通信的安全性,保护用户数据与隐私。

8

2026.01.29

热门下载

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

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
Kotlin 教程
Kotlin 教程

共23课时 | 3万人学习

C# 教程
C# 教程

共94课时 | 7.9万人学习

Java 教程
Java 教程

共578课时 | 52.9万人学习

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

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号