用Collectors.groupingBy按时间段统计订单,需先将时间字段归一化为逻辑时段(如小时、天、周或自定义区间)作为分组key,再结合下游收集器实现计数、求和或列表聚合,并注意时区转换与Java 8+时间API一致性。

用 Collectors.groupingBy 对订单数据按时间段做分布统计,核心在于把时间字段“归一化”为可分组的逻辑时段(如小时、天、周、自定义区间),再以该时段为 key 进行分组聚合。
将时间字段映射为时间段 key
不能直接用原始 LocalDateTime 或 Timestamp 分组——那会每条记录都不同。需先转换成代表“某一时段”的统一值:
-
按小时统计:取
localDateTime.truncatedTo(ChronoUnit.HOURS) -
按天统计:用
localDateTime.toLocalDate()或localDateTime.truncatedTo(ChronoUnit.DAYS) -
按周统计(周一为起点):用
localDateTime.with(DayOfWeek.MONDAY).truncatedTo(ChronoUnit.DAYS) -
按自定义时间范围(如每2小时一档):计算偏移量,例如
localDateTime.minusHours(localDateTime.getHour() % 2).truncatedTo(ChronoUnit.HOURS)
使用 groupingBy + 下游收集器完成统计
分组后通常还需聚合,比如统计数量、求金额总和、收集订单列表等:
-
统计每小时订单数:
groupingBy(hourKeyMapper, counting()) -
统计每日订单总金额:
groupingBy(dateKeyMapper, summingDouble(o -> o.getAmount())) -
获取每半天的订单列表:
groupingBy(ampmKeyMapper, mapping(Function.identity(), toList()))
注意:若需多级统计(如“按天 → 再按状态分组”),可用嵌套 groupingBy,例如 groupingBy(dateMapper, groupingBy(Order::getStatus))。
处理时区与日期边界问题
订单时间常为 UTC 或带时区时间,直接截断可能跨日。建议统一转为业务所在时区再计算:
- 用
order.getCreateTime().withZoneSameInstant(ZoneId.of("Asia/Shanghai"))转时区 - 再调用
toLocalDateTime()或truncatedTo(...),确保“当天 00:00”符合业务定义 - 避免用
new Date().getHours()等过时 API,保持 Java 8+ 时间 API 一致性
完整示例:按两小时区间统计订单数与总额
假设 Order 类有 getCreateTime() : LocalDateTime 和 getAmount() : double
Map<localdatetime map.entry double>> result = orders.stream()
.collect(Collectors.groupingBy(
o -> o.getCreateTime()
.withMinute(0).withSecond(0).withNano(0) // 先清空分秒
.minusHours(o.getCreateTime().getHour() % 2), // 对齐到最近的偶数小时起点
Collectors.teeing(
Collectors.counting(),
Collectors.summingDouble(Order::getAmount),
(count, sum) -> new AbstractMap.SimpleEntry(count, sum)
)
));</localdatetime>
这样得到的 key 是每个两小时区间的起始时刻(如 10:00、12:00),value 是该区间内订单总数与金额总和。










