博客时间字段重构:从 Date 到 LocalDateTime(AI 辅助学习 Java 8)
前两篇学了 LocalDateTime 基础和 DateTimeFormatter。今天在一个完整的博客项目中,把时间字段从旧的
Date全面升级到LocalDateTime,实现"今日发布"、"本周发布"、"月度统计"等实用功能。
一、旧版 Date 的问题
旧版实体类
public class BlogPost {
private Long id;
private String title;
private Date createTime; // java.util.Date
private Date updateTime; // java.util.Date
}
旧版打印输出
创建时间: Fri Jun 05 10:30:00 CST 2026
更新时间: Fri Jun 12 15:20:00 CST 2026
四个核心问题:
- 格式不直观:
Fri Jun 05 10:30:00 CST 2026谁看得懂? - 线程不安全:
Date是可变对象,SimpleDateFormat也不是线程安全的 - 时区混乱:
CST可能指中国标准时间,也可能指美国中部时间 - API 贫乏:想获取"本月第一天"需要大量 Calendar 操作
二、新版 LocalDateTime 实体类
public class BlogPost {
private final Long id;
private final String title;
private final LocalDateTime createTime;
private final LocalDateTime updateTime;
BlogPost(Long id, String title, LocalDateTime createTime, LocalDateTime updateTime) {
this.id = id;
this.title = title;
this.createTime = createTime;
this.updateTime = updateTime;
}
// getter 省略
}
新版打印输出
创建: 2026-06-05 10:30
更新: 2026-06-12 15:20
四大优势:不可变、线程安全、格式清晰、API 丰富。
三、今日发布
LocalDate today = LocalDate.now();
List<BlogPost> todayPosts = blogs.stream()
.filter(b -> b.getCreateTime().toLocalDate().equals(today))
.collect(Collectors.toList());
System.out.println("今日发布: " + todayPosts.size() + " 篇");
一行 filter 搞定,不需要手动比较毫秒级时间戳。
四、本周发布
LocalDate weekStart = today.with(TemporalAdjusters.previousOrSame(DayOfWeek.MONDAY));
List<BlogPost> weekPosts = blogs.stream()
.filter(b -> !b.getCreateTime().toLocalDate().isBefore(weekStart))
.collect(Collectors.toList());
System.out.println("本周发布 (从 " + weekStart + " 起): " + weekPosts.size() + " 篇");
TemporalAdjusters.previousOrSame(DayOfWeek.MONDAY) 一行算出本周一,不需要判断今天是星期几再减天数。
五、本月发布
LocalDate monthStart = today.with(TemporalAdjusters.firstDayOfMonth());
List<BlogPost> monthPosts = blogs.stream()
.filter(b -> !b.getCreateTime().toLocalDate().isBefore(monthStart))
.collect(Collectors.toList());
同样一行搞定本月第一天。
六、N 天内发布
List<BlogPost> recentPosts = blogs.stream()
.filter(b -> ChronoUnit.DAYS.between(b.getCreateTime().toLocalDate(), today) <= 7)
.collect(Collectors.toList());
ChronoUnit.DAYS.between 比 Date.getTime() 除以 86400000 清晰得多。
七、月度发文统计
Map<String, Long> monthlyCount = blogs.stream()
.collect(Collectors.groupingBy(
b -> b.getCreateTime().format(DateTimeFormatter.ofPattern("yyyy-MM")),
Collectors.counting()
));
// 输出: {2026-05=2, 2026-06=5}
按月份格式化分组,一行完成统计。
八、按星期统计
Map<DayOfWeek, Long> dayOfWeekCount = blogs.stream()
.collect(Collectors.groupingBy(
b -> b.getCreateTime().getDayOfWeek(),
Collectors.counting()
));
// 输出: {WEDNESDAY=1, MONDAY=2, FRIDAY=3, SATURDAY=1}
直接调用 getDayOfWeek() 获取枚举值,分组结果一目了然。
九、发文周期计算
LocalDateTime earliest = blogs.stream()
.map(BlogPost::getCreateTime)
.min(LocalDateTime::compareTo)
.orElse(LocalDateTime.now());
LocalDateTime latest = blogs.stream()
.map(BlogPost::getCreateTime)
.max(LocalDateTime::compareTo)
.orElse(LocalDateTime.now());
long totalDays = ChronoUnit.DAYS.between(earliest.toLocalDate(), latest.toLocalDate());
double avgDays = totalDays * 1.0 / (blogs.size() - 1);
System.out.println(String.format("发文周期: 平均每 %.1f 天发一篇", avgDays));
// 输出: 发文周期: 平均每 4.8 天发一篇
十、最近更新
BlogPost lastUpdated = blogs.stream()
.max((a, b) -> a.getUpdateTime().compareTo(b.getUpdateTime()))
.orElse(null);
DateTimeFormatter fmt = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm");
System.out.println("最近更新: " + lastUpdated.getTitle()
+ " (" + lastUpdated.getUpdateTime().format(fmt) + ")");
// 输出: 最近更新: 今日新文章 (2026-06-13 07:00)
十一、运行结果
=== 旧版 Date 方式(模拟) ===
创建时间: Fri Jun 05 10:30:00 CST 2026
更新时间: Fri Jun 12 15:20:00 CST 2026
问题: 格式不直观、时区混乱、线程不安全、可变对象
=== 新版 LocalDateTime 方式 ===
标题: Lambda 入门
创建: 2026-06-05 10:30
更新: 2026-06-12 15:20
优势: 不可变、线程安全、格式清晰、API 丰富
=== 博客时间查询 ===
今日发布 (2026-06-13): 1 篇
- 今日新文章
本周发布 (从 2026-06-08 起): 4 篇
本月发布 (从 2026-06-01 起): 5 篇
7天内发布: 4 篇
=== 博客时间统计 ===
月度发文: {2026-05=2, 2026-06=5}
按星期统计: {WEDNESDAY=1, MONDAY=2, FRIDAY=3, SATURDAY=1}
发文最多的月份: 2026-06
发文周期: 平均每 4.8 天发一篇
最近更新: 今日新文章 (2026-06-13 07:00)
十二、Date → LocalDateTime 迁移对照表
| 旧版(Date) | 新版(LocalDateTime) |
|---|---|
new Date() | LocalDateTime.now() |
new Date(2026-1900, 5, 13) | LocalDateTime.of(2026, 6, 13, 0, 0) |
SimpleDateFormat.parse() | LocalDateTime.parse() |
date.getTime() | ChronoUnit.MILLIS.between(a, b) |
Calendar.add(Calendar.DAY, 1) | dateTime.plusDays(1) |
Calendar.getFirstDayOfMonth() | date.with(TemporalAdjusters.firstDayOfMonth()) |
十三、本周学习总结
第六周我们学习了 Java 8 新时间 API,完整路线:
周二:为什么 Date/Calendar 该被淘汰?LocalDateTime 详解
周四:DateTimeFormatter 自定义格式、时区处理
周六:博客时间字段重构:从 Date 到 LocalDateTime(本篇)
核心收获:
| 场景 | 用哪个 |
|---|---|
| 只需要日期 | LocalDate |
| 只需要时间 | LocalTime |
| 日期+时间 | LocalDateTime |
| 带时区 | ZonedDateTime |
| 自定义格式 | DateTimeFormatter.ofPattern() |
| 可选部分 | DateTimeFormatterBuilder |
| 本月第一天 | with(TemporalAdjusters.firstDayOfMonth()) |
| 下周一 | with(TemporalAdjusters.next(DayOfWeek.MONDAY)) |
| 相差天数 | ChronoUnit.DAYS.between(a, b) |
| 时区转换 | withZoneSameInstant() |
关键原则:
- 所有时间对象不可变,操作返回新实例
DateTimeFormatter是线程安全的,可定义为常量- 用
TemporalAdjusters一行搞定各种"某天"计算 - 用
ChronoUnit.between替代毫秒级除法
十四、下周预告
第七周进入 方法引用 & 构造器引用,用 :: 双冒号让 Lambda 表达式更简洁。
系列文章导航:
更多请关注我的微信公众号,感谢!