在Java业务开发中,处理包含日期字段的对象集合时,按照日期维度对数据进行归类是高频需求,比如统计每日订单量、按月汇总用户消费金额等场景都需要用到日期分组能力。不同的业务场景对日期精度要求不同,对应的实现策略也存在差异,下面介绍几种常用的实现方式。

基础场景准备
首先定义一个包含日期属性的示例对象,后续所有分组操作都基于该对象列表展开:
import java.time.LocalDateTime;
public class Order {
private String orderId;
private LocalDateTime createTime;
private Double amount;
public Order(String orderId, LocalDateTime createTime, Double amount) {
this.orderId = orderId;
this.createTime = createTime;
this.amount = amount;
}
// getter和setter方法
public String getOrderId() {
return orderId;
}
public LocalDateTime getCreateTime() {
return createTime;
}
public Double getAmount() {
return amount;
}
}
按日期分组的核心策略
1. 使用Stream API的groupingBy方法
Java 8引入的Stream API提供了Collectors.groupingBy方法,可以非常简洁地实现分组逻辑,这也是目前最推荐的实现方式。
按天分组
如果需要按照创建日期的天维度分组,只需要将日期转换为当天的起始时间作为分组键即可:
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
public class DateGroupDemo {
public static void main(String[] args) {
// 构造测试数据
List<Order> orderList = new ArrayList<>();
orderList.add(new Order("001", LocalDateTime.of(2024, 3, 10, 14, 30), 100.0));
orderList.add(new Order("002", LocalDateTime.of(2024, 3, 10, 16, 45), 200.0));
orderList.add(new Order("003", LocalDateTime.of(2024, 3, 11, 9, 20), 150.0));
// 按天分组,将日期格式化为yyyy-MM-dd字符串作为分组键
DateTimeFormatter dayFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
Map<String, List<Order>> orderByDay = orderList.stream()
.collect(Collectors.groupingBy(order ->
order.getCreateTime().format(dayFormatter)));
// 输出分组结果
orderByDay.forEach((date, orders) -> {
System.out.println("日期:" + date + ",订单数量:" + orders.size());
});
}
}
按月分组
按月分组的逻辑和按天分组类似,只需要调整日期格式即可:
import java.time.format.DateTimeFormatter;
import java.util.Map;
import java.util.stream.Collectors;
public class MonthGroupDemo {
public static void main(String[] args) {
List<Order> orderList = new ArrayList<>();
orderList.add(new Order("001", LocalDateTime.of(2024, 3, 10, 14, 30), 100.0));
orderList.add(new Order("002", LocalDateTime.of(2024, 3, 25, 16, 45), 200.0));
orderList.add(new Order("003", LocalDateTime.of(2024, 4, 5, 9, 20), 150.0));
// 按月分组,格式化为yyyy-MM
DateTimeFormatter monthFormatter = DateTimeFormatter.ofPattern("yyyy-MM");
Map<String, List<Order>> orderByMonth = orderList.stream()
.collect(Collectors.groupingBy(order ->
order.getCreateTime().format(monthFormatter)));
orderByMonth.forEach((month, orders) -> {
System.out.println("月份:" + month + ",订单总金额:" +
orders.stream().mapToDouble(Order::getAmount).sum());
});
}
}
2. 传统循环实现分组
如果项目使用的Java版本低于8,无法使用Stream API,也可以通过传统循环实现分组逻辑:
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class LoopGroupDemo {
public static void main(String[] args) {
List<Order> orderList = new ArrayList<>();
orderList.add(new Order("001", LocalDateTime.of(2024, 3, 10, 14, 30), 100.0));
orderList.add(new Order("002", LocalDateTime.of(2024, 3, 10, 16, 45), 200.0));
orderList.add(new Order("003", LocalDateTime.of(2024, 3, 11, 9, 20), 150.0));
DateTimeFormatter dayFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
Map<String, List<Order>> orderByDay = new HashMap<>();
for (Order order : orderList) {
String dayKey = order.getCreateTime().format(dayFormatter);
// 如果分组键不存在,先初始化列表
if (!orderByDay.containsKey(dayKey)) {
orderByDay.put(dayKey, new ArrayList<>());
}
orderByDay.get(dayKey).add(order);
}
orderByDay.forEach((date, orders) -> {
System.out.println("日期:" + date + ",订单数量:" + orders.size());
});
}
}
分组后的常见处理操作
分组结果排序
默认情况下,HashMap的遍历顺序是不固定的,如果需要按照日期顺序输出分组结果,可以对键进行排序:
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
public class SortedGroupDemo {
public static void main(String[] args) {
List<Order> orderList = new ArrayList<>();
orderList.add(new Order("001", LocalDateTime.of(2024, 3, 10, 14, 30), 100.0));
orderList.add(new Order("002", LocalDateTime.of(2024, 3, 12, 16, 45), 200.0));
orderList.add(new Order("003", LocalDateTime.of(2024, 3, 11, 9, 20), 150.0));
DateTimeFormatter dayFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
Map<String, List<Order>> orderByDay = orderList.stream()
.collect(Collectors.groupingBy(order ->
order.getCreateTime().format(dayFormatter)));
// 按照日期字符串排序输出,注意日期格式需要符合字典序
orderByDay.entrySet().stream()
.sorted(Map.Entry.comparingByKey())
.forEach(entry -> {
System.out.println("日期:" + entry.getKey() + ",订单数量:" + entry.getValue().size());
});
}
}
分组后聚合计算
除了简单的分组,很多时候还需要对分组后的数据进行聚合,比如求和、计数、求平均值等,Stream API也提供了对应的支持:
import java.time.format.DateTimeFormatter;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
public class AggregateGroupDemo {
public static void main(String[] args) {
List<Order> orderList = new ArrayList<>();
orderList.add(new Order("001", LocalDateTime.of(2024, 3, 10, 14, 30), 100.0));
orderList.add(new Order("002", LocalDateTime.of(2024, 3, 10, 16, 45), 200.0));
orderList.add(new Order("003", LocalDateTime.of(2024, 3, 11, 9, 20), 150.0));
DateTimeFormatter dayFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
// 按天分组并直接计算每组的订单总金额
Map<String, Double> dayTotalAmount = orderList.stream()
.collect(Collectors.groupingBy(
order -> order.getCreateTime().format(dayFormatter),
Collectors.summingDouble(Order::getAmount)
));
dayTotalAmount.forEach((date, total) -> {
System.out.println("日期:" + date + ",总金额:" + total);
});
}
}
注意事项
- 日期格式选择:分组使用的日期格式字符串需要符合业务需求,同时如果后续需要排序,格式最好满足字典序,比如yyyy-MM-dd格式就比yyyy年MM月dd日更适合排序。
- 时区问题:如果系统涉及多时区场景,需要先统一时区再处理日期,避免出现分组错误。
- 空值处理:如果对象列表中存在createTime为null的情况,需要提前过滤或者做空值判断,避免分组时出现空指针异常。
Java日期分组对象列表MapStream_API修改时间:2026-07-14 09:30:36