使用Java Stream实现多条件过滤、按月分组与数据汇总

本文将深入探讨如何利用Java 8及更高版本的Stream API,高效地处理复杂数据聚合任务。我们将通过一个实际案例,演示如何对数据进行多条件过滤、按日期(月份)和事件类型进行分组,并计算每个分组的总数,最终将结果转换为指定的数据传输对象,并进行排序。

1. 场景概述与数据模型

在日常开发中,我们经常需要从原始数据中提取有价值的信息。例如,我们可能需要分析员工的入职(JOIN)和离职(EXIT)事件,按月份和事件类型统计总人数。

假设我们有以下 Person 类来存储员工事件信息:

import java.time.LocalDate;

public class Person {
    private String id;
    private String name;
    private String surname;
    private State event; // JOIN, EXIT (State是一个枚举类型)
    private Object value; // 示例中未直接使用,但可能存在
    private LocalDate eventDate;

    public Person(String id, State event, LocalDate eventDate) {
        this.id = id;
        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 + '}'; } }

其中 State 是一个枚举类型,定义了不同的事件状态:

public enum State {
    JOIN, EXIT, OTHER // 假设还有其他事件类型,例如OTHER
}

我们的目标是生成一个 List,其中 DTO 包含月份、事件类型和该类型在当月的总人数:

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,它自动提供了 equals()、hashCode() 和 toString() 方法,非常适合作为不可变的数据载体:

// Java 16+ 推荐使用 record
public record MonthState(int month, State info) {}

如果使用 Java 8-15,则需要手动创建一个类并重写 equals() 和 hashCode() 方法,以确保分组逻辑的正确性:

// Java 8-15 版本的 MonthState
import java.util.Objects;

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);
    }

    @Override
    public String toString() {
        return "MonthState{" +
               "month=" + month +
               ", info=" + info +
               '}';
    }
}