Java 巩固进阶 · 第 22 天主题日期时间 APIJDK8 新版—— 告别 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 开始DatedatenewDate();CalendarcalCalendar.getInstance();intmonthcal.get(Calendar.MONTH);// 返回 0-111 月是 0极易出错// ❌ 痛点 2线程不安全高并发必炸SimpleDateFormatsdfnewSimpleDateFormat(yyyy-MM-dd);// 多线程共享 sdf 对象 → 日期解析错乱/抛异常// 原因内部维护了 Calendar 对象状态可变// ❌ 痛点 3API 繁琐// 计算两个日期相差天数需要手动转毫秒再计算易忽略时区/闰年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数据库存储、分布式 ID2. 基本用法⭐ 背下来// 1. 获取当前时间LocalDatetodayLocalDate.now();LocalDateTimenowLocalDateTime.now();// 2. 指定时间LocalDatebirthdayLocalDate.of(2000,1,1);// 2000-01-01LocalDateTimemeetingLocalDateTime.of(2024,4,2,14,30);// 3. 获取字段月份从 1 开始✅ 修复旧版坑intyearnow.getYear();intmonthnow.getMonthValue();// 1-12intdaynow.getDayOfMonth();// 4. 时间计算不可变对象返回新实例LocalDateTimetomorrownow.plusDays(1);LocalDateTimenextMonthnow.plusMonths(1);LocalDateTimelastYearnow.minusYears(1);// 5. 比较大小booleanisBeforetoday.isBefore(birthday);booleanisAftertoday.isAfter(birthday);3. ⚠️ 关键特性不可变性LocalDateTimedtLocalDateTime.now();dt.plusHours(1);// ❌ 无效LocalDateTime 是不可变的// ✅ 正确接收返回值LocalDateTimenewDtdt.plusHours(1);记忆口诀“JDK8 时间皆不可变修改操作必接收返回值”三、格式化与解析DateTimeFormatter线程安全1. 标准用法// ✅ 推荐使用预定义格式DateTimeFormatterformatterDateTimeFormatter.ofPattern(yyyy-MM-dd HH:mm:ss);// 格式化时间 → 字符串StringstrLocalDateTime.now().format(formatter);System.out.println(str);// 2024-04-02 14:30:00// 解析字符串 → 时间LocalDateTimedtLocalDateTime.parse(2024-04-02 14:30:00,formatter);2. ️ 线程安全验证对比 SimpleDateFormat// ❌ 旧版多线程共享 SimpleDateFormat 会报错staticSimpleDateFormatsdfnewSimpleDateFormat(yyyy-MM-dd);// 多线程调用 sdf.parse() → NumberFormatException / 日期错乱// ✅ 新版DateTimeFormatter 不可变线程安全staticDateTimeFormatterformatterDateTimeFormatter.ofPattern(yyyy-MM-dd);// 多线程共享 formatter → 安全无需 ThreadLocal3. SpringBoot 集成JSON 序列化// 实体类字段publicclassOrder{// ✅ 方式 1字段注解推荐JsonFormat(patternyyyy-MM-dd HH:mm:ss,timezoneGMT8)privateLocalDateTimecreateTime;// ✅ 方式 2全局配置application.yml// spring.jackson.date-formatyyyy-MM-dd HH:mm:ss// spring.jackson.time-zoneGMT8}生产坑点默认LocalDateTime序列化可能是数组[2024,4,2,14,30,0]必须配JsonFormat时区问题数据库存 UTC展示转 GMT8建议统一用timezone GMT8四、时间计算Period Duration ⏱️1. Period日期间隔LocalDatestartLocalDate.of(2024,1,1);LocalDateendLocalDate.of(2024,12,31);// 计算间隔PeriodperiodPeriod.between(start,end);System.out.println(相差period.getYears()年period.getMonths()月period.getDays()天);// 直接获取天数longdaysChronoUnit.DAYS.between(start,end);// ✅ 推荐更直观2. Duration时间间隔LocalDateTimestartLocalDateTime.now();// 模拟业务Thread.sleep(1000);LocalDateTimeendLocalDateTime.now();// 计算秒数/毫秒数DurationdurationDuration.between(start,end);longsecondsduration.getSeconds();longmillisduration.toMillis();3. 实战计算入职天数publiclonggetWorkDays(LocalDate入职日期){returnChronoUnit.DAYS.between(入职日期LocalDate.now());}五、时区与时间戳Instant ZonedDateTime 1. Instant时间戳// 获取当前时间戳秒longepochSecondInstant.now().getEpochSecond();// 时间戳 → 时间LocalDateTimedtLocalDateTime.ofInstant(Instant.now(),ZoneId.systemDefault());2. ZonedDateTime带时区// 指定时区ZonedDateTimebeijingTimeZonedDateTime.now(ZoneId.of(Asia/Shanghai));ZonedDateTimetokyoTimeZonedDateTime.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)) */publicclassCouponChecker{publicstaticStringcheckStatus(LocalDateTimestart,LocalDateTimeend,LocalDateTimenow){// TODO: 实现逻辑}}任务 3计算工龄/司龄精确到年月/** * 要求 * 1. 输入入职日期LocalDate * 2. 输出X 年 Y 月 Z 天 * 3. 使用 Period.between 计算 * 4. 处理闰年/大小月差异Period 已自动处理 * * 挑战 * - 如果入职日期是 2 月 29 日明年没有 2 月 29 日怎么办 * - 测试2024-02-29 入职2025-02-28 算满 1 年吗 */任务 4SpringBoot 集成测试/** * 要求 * 1. 创建 Entity 类包含 LocalDateTime 字段 * 2. 创建 Controller返回 JSON 数据 * 3. 验证JSON 中时间字段是否为字符串格式而非数组 * 4. 验证时区是否正确GMT8 * * 配置 * - 添加 JsonFormat(pattern ..., timezone GMT8) * - 或配置 application.yml 全局 Jackson 格式 */ 第 22 天 · 核心总结极简背诵版新旧 API 对比❌ 旧Date/Calendar/SimpleDateFormat可变、线程不安全、月份从 0 开始 ✅ 新LocalDate/LocalDateTime/DateTimeFormatter不可变、线程安全、语义清晰核心类选型只需日期 →LocalDate只需时间 →LocalTime日期时间 →LocalDateTime最常用 时间戳 →Instant格式化铁律// ✅ 线程安全可静态共享privatestaticfinalDateTimeFormatterformatterDateTimeFormatter.ofPattern(yyyy-MM-dd HH:mm:ss);Stringstrdt.format(formatter);LocalDateTimedtLocalDateTime.parse(str,formatter);SpringBoot 集成✅ 实体类字段加JsonFormat(pattern ..., timezone GMT8)✅ 数据库字段类型datetime↔LocalDateTime❌ 避免返回[2024,4,2...]数组格式时间计算// 日期间隔Period.between(start,end);// 时间间隔Duration.between(start,end);// 直接算天数推荐ChronoUnit.DAYS.between(start,end);