You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Java Stream的groupingBy中按条件实现多方式聚合?

Java Stream实现按日期分组并按规则聚合TimeInterval数据

核心思路

要实现需求,需要分两步处理:

  • 按日期分组:从ZonedDateTime中提取LocalDate作为分组键
  • 按规则聚合:对每组数据判断是否全为空值,再执行对应的计算逻辑

具体实现代码

首先定义辅助类和方法,用来处理单个区间的乘积计算及有效性判断:

// 辅助类:存储单个区间的计算结果和是否为有效数据
private static class IntervalCalcResult {
    double product;
    boolean hasValidValue;

    IntervalCalcResult(double product, boolean hasValidValue) {
        this.product = product;
        this.hasValidValue = hasValidValue;
    }

    // getter方法
    public double getProduct() {
        return product;
    }

    public boolean isHasValidValue() {
        return hasValidValue;
    }
}

// 计算单个TimeInterval的乘积,同时标记是否为有效数据
private static IntervalCalcResult calculateIntervalProduct(TimeInterval interval) {
    Double v1 = interval.getValue1() != null ? interval.getValue1() : 0.0;
    Double v2 = interval.getValue2() != null ? interval.getValue2() : 0.0;
    // 只要value1或value2不为null,就算有效数据
    boolean hasValid = interval.getValue1() != null || interval.getValue2() != null;
    return new IntervalCalcResult(v1 * v2, hasValid);
}

然后通过Stream完成分组和聚合逻辑:

import java.time.LocalDate;
import java.time.ZonedDateTime;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

public class TimeIntervalAggregator {

    public static Map<LocalDate, Double> aggregateByDate(List<TimeInterval> intervals) {
        return intervals.stream()
                .collect(Collectors.groupingBy(
                        // 提取日期作为分组键
                        interval -> interval.getTime().toLocalDate(),
                        Collectors.collectingAndThen(
                                // 将每个TimeInterval转换为计算结果对象
                                Collectors.mapping(TimeIntervalAggregator::calculateIntervalProduct, Collectors.toList()),
                                results -> {
                                    // 判断当前日期下是否存在有效数据
                                    boolean anyValid = results.stream().anyMatch(IntervalCalcResult::isHasValidValue);
                                    if (!anyValid) {
                                        return null; // 全为空则返回null
                                    }
                                    // 对所有有效/替换后的乘积求和
                                    return results.stream()
                                            .mapToDouble(IntervalCalcResult::getProduct)
                                            .sum();
                                }
                        )
                ));
    }

    // 测试代码
    public static void main(String[] args) {
        List<TimeInterval> data = List.of(
                new TimeInterval(ZonedDateTime.parse("2018-01-01T00:00"), 1.0, 2.0),
                new TimeInterval(ZonedDateTime.parse("2018-01-01T08:00"), null, null),
                new TimeInterval(ZonedDateTime.parse("2018-01-01T16:00"), 5.0, 6.0),
                new TimeInterval(ZonedDateTime.parse("2018-01-02T00:00"), 1.0, 2.0),
                new TimeInterval(ZonedDateTime.parse("2018-01-02T08:00"), 3.0, 4.0),
                new TimeInterval(ZonedDateTime.parse("2018-01-02T16:00"), 5.0, 6.0),
                new TimeInterval(ZonedDateTime.parse("2018-01-03T00:00"), null, null),
                new TimeInterval(ZonedDateTime.parse("2018-01-03T08:00"), null, null),
                new TimeInterval(ZonedDateTime.parse("2018-01-03T16:00"), null, null)
        );

        Map<LocalDate, Double> result = aggregateByDate(data);
        result.forEach((date, value) -> System.out.printf("%s, %s%n", date, value));
    }
}

// 补充TimeInterval类的getter方法
class TimeInterval {
    private ZonedDateTime time;
    private Double value1;
    private Double value2;

    public TimeInterval(ZonedDateTime time, Double value1, Double value2) {
        this.time = time;
        this.value1 = value1;
        this.value2 = value2;
    }

    public ZonedDateTime getTime() {
        return time;
    }

    public Double getValue1() {
        return value1;
    }

    public Double getValue2() {
        return value2;
    }
}

代码说明

  1. 分组逻辑:通过Collectors.groupingBy按LocalDate对数据分组,确保同一日期的区间被归类到一起
  2. 聚合逻辑:
    • 先用mapping将每个TimeInterval转换为包含乘积和有效性标记的对象,提前处理空值替换
    • 再通过collectingAndThen做最终判断:如果组内全是无效数据则返回null,否则对所有乘积求和
  3. 空值处理:在辅助方法中把null的value1/value2替换为0,避免计算时出现空指针异常

运行测试代码后,输出结果与预期完全一致:

2018-01-01, 32.0
2018-01-02, 44.0
2018-01-03, null

内容的提问来源于stack exchange,提问作者Jordan Jordanovski

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.16 17:42:32