JAVA重点基础、进阶知识及易错点总结(22)日期时间 API(JDK8 新版)
·
🚀 Java 巩固进阶 · 第 22 天
主题:日期时间 API(JDK8 新版)—— 告别 Date 的线程不安全
📅 进度概览:今天学习 Java 8 引入的全新日期时间 API。彻底抛弃老旧的
Date和Calendar,掌握线程安全、设计优雅的java.time包。这是现代 Java 开发的标准规范。💡 核心价值:
- 线程安全:
DateTimeFormatter和LocalDateTime不可变且线程安全,解决SimpleDateFormat并发痛点。- 语义清晰:
LocalDate(日期)、LocalTime(时间)、LocalDateTime(日期时间)分离,API 设计更直观。- 框架集成:SpringBoot 默认支持 JDK8 时间类型,JSON 序列化需配置
@JsonFormat。- 面试高频:
SimpleDateFormat线程不安全原因、JDK8 新 API 优势是必考题。
一、为什么抛弃旧 API?历史包袱太重 🎒
1. 旧版 API 的三大痛点
// ❌ 痛点 1:设计混乱(月份从 0 开始!)
Date date = new Date();
Calendar cal = Calendar.getInstance();
int month = cal.get(Calendar.MONTH); // 返回 0-11,1 月是 0!极易出错
// ❌ 痛点 2:线程不安全(高并发必炸!)
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
// 多线程共享 sdf 对象 → 日期解析错乱/抛异常
// 原因:内部维护了 Calendar 对象,状态可变
// ❌ 痛点 3:API 繁琐
// 计算两个日期相差天数,需要手动转毫秒再计算,易忽略时区/闰年
2. JDK8 新 API 的核心优势
┌─────────────────────────────────────┐
│ ✅ java.time 包(JSR-310 标准) │
│ │
│ • 不可变Immutable:线程安全 │
│ • 语义清晰:日期/时间/时区分离 │
│ • 计算方便:plus/minus/daysBetween │
│ • 格式化安全:DateTimeFormatter │
└─────────────────────────────────────┘
💡 一句话原则:
“新项目禁止使用Date/Calendar/SimpleDateFormat,统一用 JDK8java.time”
二、核心类详解:LocalDate / LocalTime / LocalDateTime 📅
1. 常用类对比
| 类 | 含义 | 示例 | 适用场景 |
|---|---|---|---|
LocalDate |
日期(年月日) | 2024-04-02 | 生日、入职日期、保质期 |
LocalTime |
时间(时分秒) | 14:30:00 | 开门时间、会议时间 |
LocalDateTime |
日期 + 时间 | 2024-04-02T14:30:00 | 订单创建时间、日志时间戳 |
Instant |
时间戳(秒/纳秒) | 1712034600 | 数据库存储、分布式 ID |
2. 基本用法(⭐ 背下来!)
// 🎯 1. 获取当前时间
LocalDate today = LocalDate.now();
LocalDateTime now = LocalDateTime.now();
// 🎯 2. 指定时间
LocalDate birthday = LocalDate.of(2000, 1, 1); // 2000-01-01
LocalDateTime meeting = LocalDateTime.of(2024, 4, 2, 14, 30);
// 🎯 3. 获取字段(月份从 1 开始!✅ 修复旧版坑)
int year = now.getYear();
int month = now.getMonthValue(); // 1-12
int day = now.getDayOfMonth();
// 🎯 4. 时间计算(不可变对象,返回新实例)
LocalDateTime tomorrow = now.plusDays(1);
LocalDateTime nextMonth = now.plusMonths(1);
LocalDateTime lastYear = now.minusYears(1);
// 🎯 5. 比较大小
boolean isBefore = today.isBefore(birthday);
boolean isAfter = today.isAfter(birthday);
3. ⚠️ 关键特性:不可变性
LocalDateTime dt = LocalDateTime.now();
dt.plusHours(1); // ❌ 无效!LocalDateTime 是不可变的
// ✅ 正确:接收返回值
LocalDateTime newDt = dt.plusHours(1);
💡 记忆口诀:
“JDK8 时间皆不可变,修改操作必接收返回值”
三、格式化与解析:DateTimeFormatter(线程安全)🔤
1. 标准用法
// ✅ 推荐:使用预定义格式
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
// 格式化:时间 → 字符串
String str = LocalDateTime.now().format(formatter);
System.out.println(str); // 2024-04-02 14:30:00
// 解析:字符串 → 时间
LocalDateTime dt = LocalDateTime.parse("2024-04-02 14:30:00", formatter);
2. 🛡️ 线程安全验证(对比 SimpleDateFormat)
// ❌ 旧版:多线程共享 SimpleDateFormat 会报错
static SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
// 多线程调用 sdf.parse() → NumberFormatException / 日期错乱
// ✅ 新版:DateTimeFormatter 不可变,线程安全
static DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
// 多线程共享 formatter → 安全!无需 ThreadLocal
3. SpringBoot 集成:JSON 序列化
// 实体类字段
public class Order {
// ✅ 方式 1:字段注解(推荐)
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private LocalDateTime createTime;
// ✅ 方式 2:全局配置(application.yml)
// spring.jackson.date-format=yyyy-MM-dd HH:mm:ss
// spring.jackson.time-zone=GMT+8
}
💡 生产坑点:
- 默认
LocalDateTime序列化可能是数组[2024,4,2,14,30,0],必须配@JsonFormat!- 时区问题:数据库存 UTC,展示转 GMT+8,建议统一用
timezone = "GMT+8"
四、时间计算:Period & Duration ⏱️
1. Period(日期间隔)
LocalDate start = LocalDate.of(2024, 1, 1);
LocalDate end = LocalDate.of(2024, 12, 31);
// 计算间隔
Period period = Period.between(start, end);
System.out.println("相差:" + period.getYears() + "年" +
period.getMonths() + "月" +
period.getDays() + "天");
// 直接获取天数
long days = ChronoUnit.DAYS.between(start, end); // ✅ 推荐,更直观
2. Duration(时间间隔)
LocalDateTime start = LocalDateTime.now();
// 模拟业务
Thread.sleep(1000);
LocalDateTime end = LocalDateTime.now();
// 计算秒数/毫秒数
Duration duration = Duration.between(start, end);
long seconds = duration.getSeconds();
long millis = duration.toMillis();
3. 🎯 实战:计算入职天数
public long getWorkDays(LocalDate入职日期) {
return ChronoUnit.DAYS.between(入职日期,LocalDate.now());
}
五、时区与时间戳:Instant & ZonedDateTime 🌍
1. Instant(时间戳)
// 获取当前时间戳(秒)
long epochSecond = Instant.now().getEpochSecond();
// 时间戳 → 时间
LocalDateTime dt = LocalDateTime.ofInstant(Instant.now(), ZoneId.systemDefault());
2. ZonedDateTime(带时区)
// 指定时区
ZonedDateTime beijingTime = ZonedDateTime.now(ZoneId.of("Asia/Shanghai"));
ZonedDateTime tokyoTime = ZonedDateTime.now(ZoneId.of("Asia/Tokyo"));
💡 最佳实践:
- 数据库存储:用
Instant或LocalDateTime(统一存 UTC 或统一存北京时间)- 业务展示:转为用户本地时区
- 微服务交互:统一用
Instant时间戳,避免时区歧义
六、🎯 今日实战任务:时间工具类封装
任务 1:封装"日期格式化工具类"
/**
* 要求:
* 1. 创建 DateUtils 类,所有方法静态
* 2. 定义常用格式常量:YYYY_MM_DD, YYYY_MM_DD_HH_MM_SS
* 3. 实现 format(LocalDateTime, pattern) 和 parse(String, pattern)
* 4. 确保 DateTimeFormatter 线程安全(可定义为 static final)
*
* 💡 提示:
* - 不要每次调用都 new DateTimeFormatter
* - 处理解析异常,返回 null 或抛自定义异常
*/
任务 2:实现"优惠券过期判断"逻辑
/**
* 业务场景:判断优惠券是否可用
*
* 要求:
* 1. 输入:开始时间、结束时间、当前时间
* 2. 逻辑:当前时间在 [开始,结束] 区间内则有效
* 3. 输出:有效/已过期/未开始
* 4. 边界测试:刚好等于开始/结束时间是否算有效?
*
* 💡 关键 API:
* - !now.isBefore(start) && !now.isAfter(end)
* 或:!now.isBefore(start) && now.isBefore(end.plusDays(1))
*/
public class CouponChecker {
public static String checkStatus(LocalDateTime start, LocalDateTime end, LocalDateTime now) {
// TODO: 实现逻辑
}
}
任务 3:计算"工龄/司龄"(精确到年月)
/**
* 要求:
* 1. 输入:入职日期(LocalDate)
* 2. 输出:X 年 Y 月 Z 天
* 3. 使用 Period.between 计算
* 4. 处理闰年/大小月差异(Period 已自动处理)
*
* 💡 挑战:
* - 如果入职日期是 2 月 29 日,明年没有 2 月 29 日怎么办?
* - 测试:2024-02-29 入职,2025-02-28 算满 1 年吗?
*/
任务 4:SpringBoot 集成测试
/**
* 要求:
* 1. 创建 Entity 类,包含 LocalDateTime 字段
* 2. 创建 Controller,返回 JSON 数据
* 3. 验证:JSON 中时间字段是否为字符串格式(而非数组)
* 4. 验证:时区是否正确(GMT+8)
*
* 💡 配置:
* - 添加 @JsonFormat(pattern = "...", timezone = "GMT+8")
* - 或配置 application.yml 全局 Jackson 格式
*/
📝 第 22 天 · 核心总结(极简背诵版)
-
新旧 API 对比:
❌ 旧:Date/Calendar/SimpleDateFormat(可变、线程不安全、月份从 0 开始) ✅ 新:LocalDate/LocalDateTime/DateTimeFormatter(不可变、线程安全、语义清晰) -
核心类选型:
只需日期 → LocalDate 只需时间 → LocalTime 日期 + 时间 → LocalDateTime(最常用) 时间戳 → Instant -
格式化铁律:
// ✅ 线程安全,可静态共享 private static final DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); String str = dt.format(formatter); LocalDateTime dt = LocalDateTime.parse(str, formatter); -
SpringBoot 集成:
- ✅ 实体类字段加
@JsonFormat(pattern = "...", timezone = "GMT+8") - ✅ 数据库字段类型:
datetime↔LocalDateTime - ❌ 避免返回
[2024,4,2...]数组格式
- ✅ 实体类字段加
-
时间计算:
// 日期间隔 Period.between(start, end); // 时间间隔 Duration.between(start, end); // 直接算天数(推荐) ChronoUnit.DAYS.between(start, end);
更多推荐




所有评论(0)