0

0

Java Stream API:按共享属性聚合对象并合并日期范围

聖光之護

聖光之護

发布时间:2025-09-14 12:26:13

|

757人浏览过

|

来源于php中文网

原创

Java Stream API:按共享属性聚合对象并合并日期范围

本教程演示了如何使用Java Stream API高效地聚合一个包含日期范围和分组信息的对象列表。通过Collectors.groupingBy按指定属性分组,并从每个组中提取最早的开始日期和最晚的结束日期,从而生成精简的汇总数据。此方法适用于需要基于共同标识符合并数据记录的场景。

问题描述

在数据处理中,我们经常会遇到需要根据某个共同属性对数据记录进行分组和汇总的场景。例如,给定一个entities对象列表,每个对象包含一个开始日期(start_dt)、一个结束日期(stop_dt)和一个组编号(groupnum)。如果多个entities对象共享相同的groupnum,它们就属于同一个逻辑组。我们的目标是聚合这些对象,为每个组生成一个新的entities对象,其中新对象的start_dt是该组所有原始对象中最早的开始日期,而stop_dt是该组所有原始对象中最晚的结束日期。

原始数据结构示例:

Start Stop GroupNum
2018-11-13 2019-01-13 1
2019-01-14 2019-03-06 1
2019-03-07 2019-11-18 1
2020-08-23 2020-08-23 2
2021-11-19 2022-12-23 2

期望的聚合结果:

Start Stop GroupNum
2018-11-13 2019-11-18 1
2020-08-23 2022-12-23 2

解决方案:使用Java Stream API

Java 8引入的Stream API为处理集合数据提供了强大而灵活的工具。我们可以利用Collectors.groupingBy方法进行分组,然后对每个组进行进一步的处理以提取所需的聚合信息。

实体类定义

首先,我们需要定义Entities类,它包含start_dt、stop_dt和groupNum字段,以及相应的构造函数、getter方法和toString方法,以便于创建实例和打印输出。

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

Roboflow
Roboflow

一个为计算机视觉和机器学习提供工具和服务的平台

下载
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Objects;

public class Entities {
    private final Date start_dt;
    private final Date stop_dt;
    private final int groupNum;

    // 构造函数,接受字符串日期并解析
    public Entities(String start_dt_str, String stop_dt_str, int groupNum) throws ParseException {
        SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd");
        this.start_dt = formatter.parse(start_dt_str);
        this.stop_dt = formatter.parse(stop_dt_str);
        this.groupNum = groupNum;
    }

    // 构造函数,接受Date对象
    public Entities(Date start_dt, Date stop_dt, int groupNum) {
        this.start_dt = start_dt;
        this.stop_dt = stop_dt;
        this.groupNum = groupNum;
    }

    // Getter方法
    public Date getStart_dt() {
        return start_dt;
    }

    public Date getStop_dt() {
        return stop_dt;
    }

    public int getGroupNum() {
        return groupNum;
    }

    @Override
    public String toString() {
        SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd");
        return "Entities [start_dt=" + formatter.format(start_dt) +
               ", stop_dt=" + formatter.format(stop_dt) +
               ", groupNum=" + groupNum + "]";
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Entities entities = (Entities) o;
        return groupNum == entities.groupNum &&
               Objects.equals(start_dt, entities.start_dt) &&
               Objects.equals(stop_dt, entities.stop_dt);
    }

    @Override
    public int hashCode() {
        return Objects.hash(start_dt, stop_dt, groupNum);
    }
}

核心聚合逻辑

聚合过程主要分为以下几个步骤:

  1. 创建初始列表:首先,我们需要一个包含所有原始Entities对象的列表。
  2. 按groupNum分组:使用Collectors.groupingBy(Entities::getGroupNum)将列表中的Entities对象按groupNum分组,结果是一个Map>。
  3. 遍历分组并聚合:对Map的每个条目(Entry)进行处理。每个条目代表一个组,其键是groupNum,值是该组中所有Entities对象的列表。
  4. 提取最早和最晚日期:对于每个组的Entities列表,我们假定列表中的元素已经按日期顺序排列(即,第一个元素的start_dt最早,最后一个元素的stop_dt最晚)。因此,可以直接获取列表的第一个元素的start_dt作为聚合后的start_dt,以及最后一个元素的stop_dt作为聚合后的stop_dt。
  5. 构建新的Entities对象:使用提取到的最早start_dt、最晚stop_dt和当前的groupNum创建一个新的Entities对象。
  6. 收集结果:将所有新创建的Entities对象收集到一个新的列表中。
import java.text.ParseException;
import java.util.List;
import java.util.stream.Collectors;

public class AggregationExample {

    public static void main(String[] args) throws ParseException {
        // 1. 创建初始列表
        List<Entities> baseList = List.of(
            new Entities("2018-11-13", "2019-01-13", 1),
            new Entities("2019-01-14", "2019-03-06", 1),
            new Entities("2019-03-07", "2019-11-18", 1),
            new Entities("2020-08-23", "2020-08-23", 2),
            new Entities("2021-11-19", "2022-12-23", 2));

        // 2. 使用Stream API进行聚合
        List<Entities> result = baseList.stream()
            // 2.1. 按groupNum分组,结果为Map<Integer, List<Entities>>
            .collect(Collectors.groupingBy(Entities::getGroupNum))
            // 2.2. 将Map转换为Stream<Map.Entry<Integer, List<Entities>>>
            .entrySet().stream()
            // 2.3. 对每个分组进行映射,生成新的Entities对象
            .map(entry -> {
                List<Entities> groupEntities = entry.getValue();
                // 2.4. 提取最早的start_dt(列表第一个元素的start_dt)
                // 2.5. 提取最晚的stop_dt(列表最后一个元素的stop_dt)
                // 注意:这里假设groupEntities列表内部已按日期排序
                return new Entities(
                    groupEntities.get(0).getStart_dt(), // 获取组内第一个元素的开始日期
                    groupEntities.get(groupEntities.size() - 1).getStop_dt(), // 获取组内最后一个元素的结束日期
                    entry.getKey() // 获取当前组的groupNum
                );
            })
            // 2.6. 将结果收集为List<Entities>
            .toList(); // Java 16+,等同于.collect(Collectors.toList())

        // 打印聚合结果
        result.forEach(System.out::println);
    }
}

输出:

Entities [start_dt=2018-11-13, stop_dt=2019-11-18, groupNum=1]
Entities [start_dt=2020-08-23, stop_dt=2022-12-23, groupNum=2]

注意事项

  1. 输入列表的排序假设: 上述解决方案中,map操作直接通过groupEntities.get(0).getStart_dt()和groupEntities.get(groupEntities.size() - 1).getStop_dt()来获取最早和最晚日期。这强烈依赖于原始baseList在分组前已经按照groupNum和日期(start_dt或stop_dt)进行了排序。如果baseList的元素顺序是随机的,get(0)和get(size - 1)可能无法正确地提供最早和最晚的日期。

    更健壮的方法:如果无法保证输入列表的排序,应在map操作内部使用min和max收集器来查找日期:

    .map(entry -> {
        List<Entities> groupEntities = entry.getValue();
        Date minStartDate = groupEntities.stream()
                                         .map(Entities::getStart_dt)
                                         .min(Date::compareTo)
                                         .orElse(null); // 处理空组情况
        Date maxStopDate = groupEntities.stream()
                                        .map(Entities::getStop_dt)
                                        .max(Date::compareTo)
                                        .orElse(null); // 处理空组情况
        return new Entities(minStartDate, maxStopDate, entry.getKey());
    })

    这种方式虽然代码量稍多,但更加健壮,不依赖于原始列表的内部排序。

  2. 日期处理: 示例中使用了java.util.Date和java.text.SimpleDateFormat进行日期解析和格式化。java.util.Date是可变对象,且设计上存在一些问题。在现代Java应用中,强烈推荐使用java.time包(即JSR 310)中的LocalDate、LocalDateTime等类,它们提供了更好的API设计、线程安全性和不可变性。

  3. 异常处理: 在Entities的构造函数中,SimpleDateFormat.parse()方法会抛出ParseException。在实际应用中,应妥善处理此异常,例如通过try-catch块捕获并记录错误,或将其转换为运行时异常。

总结

通过Java Stream API,我们可以以声明式的方式优雅地解决基于共享属性进行数据分组和聚合的问题。Collectors.groupingBy是实现这一目标的关键,它将复杂的数据转换过程分解为易于理解和维护的步骤。在实际应用中,务必考虑数据源的特性(如是否已排序)以及选择合适的日期处理API,以确保解决方案的健壮性和准确性。

热门AI工具

更多
DeepSeek
DeepSeek

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

豆包大模型
豆包大模型

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

通义千问
通义千问

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

腾讯元宝
腾讯元宝

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

文心一言
文心一言

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

讯飞写作
讯飞写作

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

即梦AI
即梦AI

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

ChatGPT
ChatGPT

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

相关专题

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

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

209

2023.12.04

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

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

322

2024.02.23

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

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

292

2025.06.11

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

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

177

2025.08.07

treenode的用法
treenode的用法

​在计算机编程领域,TreeNode是一种常见的数据结构,通常用于构建树形结构。在不同的编程语言中,TreeNode可能有不同的实现方式和用法,通常用于表示树的节点信息。更多关于treenode相关问题详情请看本专题下面的文章。php中文网欢迎大家前来学习。

548

2023.12.01

C++ 高效算法与数据结构
C++ 高效算法与数据结构

本专题讲解 C++ 中常用算法与数据结构的实现与优化,涵盖排序算法(快速排序、归并排序)、查找算法、图算法、动态规划、贪心算法等,并结合实际案例分析如何选择最优算法来提高程序效率。通过深入理解数据结构(链表、树、堆、哈希表等),帮助开发者提升 在复杂应用中的算法设计与性能优化能力。

30

2025.12.22

深入理解算法:高效算法与数据结构专题
深入理解算法:高效算法与数据结构专题

本专题专注于算法与数据结构的核心概念,适合想深入理解并提升编程能力的开发者。专题内容包括常见数据结构的实现与应用,如数组、链表、栈、队列、哈希表、树、图等;以及高效的排序算法、搜索算法、动态规划等经典算法。通过详细的讲解与复杂度分析,帮助开发者不仅能熟练运用这些基础知识,还能在实际编程中优化性能,提高代码的执行效率。本专题适合准备面试的开发者,也适合希望提高算法思维的编程爱好者。

44

2026.01.06

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

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

764

2023.08.10

Go高并发任务调度与Goroutine池化实践
Go高并发任务调度与Goroutine池化实践

本专题围绕 Go 语言在高并发任务处理场景中的实践展开,系统讲解 Goroutine 调度模型、Channel 通信机制以及并发控制策略。内容包括任务队列设计、Goroutine 池化管理、资源限制控制以及并发任务的性能优化方法。通过实际案例演示,帮助开发者构建稳定高效的 Go 并发任务处理系统,提高系统在高负载环境下的处理能力与稳定性。

4

2026.03.10

热门下载

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

精品课程

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

共23课时 | 4.3万人学习

C# 教程
C# 教程

共94课时 | 11.1万人学习

Java 教程
Java 教程

共578课时 | 80.3万人学习

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

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