如何在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; } }
代码说明
- 分组逻辑:通过
Collectors.groupingBy按LocalDate对数据分组,确保同一日期的区间被归类到一起 - 聚合逻辑:
- 先用
mapping将每个TimeInterval转换为包含乘积和有效性标记的对象,提前处理空值替换 - 再通过
collectingAndThen做最终判断:如果组内全是无效数据则返回null,否则对所有乘积求和
- 先用
- 空值处理:在辅助方法中把null的
value1/value2替换为0,避免计算时出现空指针异常
运行测试代码后,输出结果与预期完全一致:
2018-01-01, 32.0 2018-01-02, 44.0 2018-01-03, null
内容的提问来源于stack exchange,提问作者Jordan Jordanovski
相关产品推荐
相关产品推荐

