当前位置: 首页 > news >正文

JAVA重点基础、进阶知识及易错点总结(22)日期时间 API(JDK8 新版)

🚀 Java 巩固进阶 · 第 22 天

主题:日期时间 API(JDK8 新版)—— 告别 Date 的线程不安全

📅 进度概览:今天学习Java 8 引入的全新日期时间 API。彻底抛弃老旧的DateCalendar,掌握线程安全、设计优雅的java.time包。这是现代 Java 开发的标准规范。

💡 核心价值

  • 线程安全DateTimeFormatterLocalDateTime不可变且线程安全,解决SimpleDateFormat并发痛点。
  • 语义清晰LocalDate(日期)、LocalTime(时间)、LocalDateTime(日期时间)分离,API 设计更直观。
  • 框架集成:SpringBoot 默认支持 JDK8 时间类型,JSON 序列化需配置@JsonFormat
  • 面试高频SimpleDateFormat线程不安全原因、JDK8 新 API 优势是必考题。

一、为什么抛弃旧 API?历史包袱太重 🎒

1. 旧版 API 的三大痛点

// ❌ 痛点 1:设计混乱(月份从 0 开始!)Datedate=newDate();Calendarcal=Calendar.getInstance();intmonth=cal.get(Calendar.MONTH);// 返回 0-11,1 月是 0!极易出错// ❌ 痛点 2:线程不安全(高并发必炸!)SimpleDateFormatsdf=newSimpleDateFormat("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. 获取当前时间LocalDatetoday=LocalDate.now();LocalDateTimenow=LocalDateTime.now();// 🎯 2. 指定时间LocalDatebirthday=LocalDate.of(2000,1,1);// 2000-01-01LocalDateTimemeeting=LocalDateTime.of(2024,4,2,14,30);// 🎯 3. 获取字段(月份从 1 开始!✅ 修复旧版坑)intyear=now.getYear();intmonth=now.getMonthValue();// 1-12intday=now.getDayOfMonth();// 🎯 4. 时间计算(不可变对象,返回新实例)LocalDateTimetomorrow=now.plusDays(1);LocalDateTimenextMonth=now.plusMonths(1);LocalDateTimelastYear=now.minusYears(1);// 🎯 5. 比较大小booleanisBefore=today.isBefore(birthday);booleanisAfter=today.isAfter(birthday);

3. ⚠️ 关键特性:不可变性

LocalDateTimedt=LocalDateTime.now();dt.plusHours(1);// ❌ 无效!LocalDateTime 是不可变的// ✅ 正确:接收返回值LocalDateTimenewDt=dt.plusHours(1);

💡记忆口诀
“JDK8 时间皆不可变,修改操作必接收返回值”


三、格式化与解析:DateTimeFormatter(线程安全)🔤

1. 标准用法

// ✅ 推荐:使用预定义格式DateTimeFormatterformatter=DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");// 格式化:时间 → 字符串Stringstr=LocalDateTime.now().format(formatter);System.out.println(str);// 2024-04-02 14:30:00// 解析:字符串 → 时间LocalDateTimedt=LocalDateTime.parse("2024-04-02 14:30:00",formatter);

2. 🛡️ 线程安全验证(对比 SimpleDateFormat)

// ❌ 旧版:多线程共享 SimpleDateFormat 会报错staticSimpleDateFormatsdf=newSimpleDateFormat("yyyy-MM-dd");// 多线程调用 sdf.parse() → NumberFormatException / 日期错乱// ✅ 新版:DateTimeFormatter 不可变,线程安全staticDateTimeFormatterformatter=DateTimeFormatter.ofPattern("yyyy-MM-dd");// 多线程共享 formatter → 安全!无需 ThreadLocal

3. SpringBoot 集成:JSON 序列化

// 实体类字段publicclassOrder{// ✅ 方式 1:字段注解(推荐)@JsonFormat(pattern="yyyy-MM-dd HH:mm:ss",timezone="GMT+8")privateLocalDateTimecreateTime;// ✅ 方式 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(日期间隔)

LocalDatestart=LocalDate.of(2024,1,1);LocalDateend=LocalDate.of(2024,12,31);// 计算间隔Periodperiod=Period.between(start,end);System.out.println("相差:"+period.getYears()+"年"+period.getMonths()+"月"+period.getDays()+"天");// 直接获取天数longdays=ChronoUnit.DAYS.between(start,end);// ✅ 推荐,更直观

2. Duration(时间间隔)

LocalDateTimestart=LocalDateTime.now();// 模拟业务Thread.sleep(1000);LocalDateTimeend=LocalDateTime.now();// 计算秒数/毫秒数Durationduration=Duration.between(start,end);longseconds=duration.getSeconds();longmillis=duration.toMillis();

3. 🎯 实战:计算入职天数

publiclonggetWorkDays(LocalDate入职日期){returnChronoUnit.DAYS.between(入职日期,LocalDate.now());}

五、时区与时间戳:Instant & ZonedDateTime 🌍

1. Instant(时间戳)

// 获取当前时间戳(秒)longepochSecond=Instant.now().getEpochSecond();// 时间戳 → 时间LocalDateTimedt=LocalDateTime.ofInstant(Instant.now(),ZoneId.systemDefault());

2. ZonedDateTime(带时区)

// 指定时区ZonedDateTimebeijingTime=ZonedDateTime.now(ZoneId.of("Asia/Shanghai"));ZonedDateTimetokyoTime=ZonedDateTime.now(ZoneId.of("Asia/Tokyo"));

💡最佳实践

  • 数据库存储:用InstantLocalDateTime(统一存 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 年吗? */

任务 4:SpringBoot 集成测试

/** * 要求: * 1. 创建 Entity 类,包含 LocalDateTime 字段 * 2. 创建 Controller,返回 JSON 数据 * 3. 验证:JSON 中时间字段是否为字符串格式(而非数组) * 4. 验证:时区是否正确(GMT+8) * * 💡 配置: * - 添加 @JsonFormat(pattern = "...", timezone = "GMT+8") * - 或配置 application.yml 全局 Jackson 格式 */

📝 第 22 天 · 核心总结(极简背诵版)

  1. 新旧 API 对比

    ❌ 旧:Date/Calendar/SimpleDateFormat(可变、线程不安全、月份从 0 开始) ✅ 新:LocalDate/LocalDateTime/DateTimeFormatter(不可变、线程安全、语义清晰)
  2. 核心类选型

    只需日期 →LocalDate只需时间 →LocalTime日期+时间 →LocalDateTime(最常用) 时间戳 →Instant
  3. 格式化铁律

    // ✅ 线程安全,可静态共享privatestaticfinalDateTimeFormatterformatter=DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");Stringstr=dt.format(formatter);LocalDateTimedt=LocalDateTime.parse(str,formatter);
  4. SpringBoot 集成

    • ✅ 实体类字段加@JsonFormat(pattern = "...", timezone = "GMT+8")
    • ✅ 数据库字段类型:datetimeLocalDateTime
    • ❌ 避免返回[2024,4,2...]数组格式
  5. 时间计算

    // 日期间隔Period.between(start,end);// 时间间隔Duration.between(start,end);// 直接算天数(推荐)ChronoUnit.DAYS.between(start,end);

http://www.jsqmd.com/news/576307/

相关文章:

  • 【Hot 100 刷题计划】 LeetCode 121. 买卖股票的最佳时机 | C++ 贪心/动态规划题解
  • 2026年郑州粉末喷涂工厂挑选指南:5步教你选对优质厂家 - 精选优质企业推荐榜
  • 阅读APP书源完全指南:打造你的个性化小说阅读生态
  • 千问3.5-2B开源可部署:模型权重托管远端,升级只需替换配置不重拉镜像
  • 安防相机WDR功能实测:逆光场景下如何拍清车牌和人脸?
  • 运算放大器相位补偿:从原理到实战的稳定性设计
  • 探索固定翼无人机编队控制:从高效协同到PX4-Autopilot落地实践
  • Qwen3.5-9B效果展示:中文新闻事件抽取+时间线生成+关联人物图谱
  • 华硕笔记本终极控制指南:3步用GHelper告别臃肿Armoury Crate
  • 2-SAT 好题分享
  • (全网最硬核)实测8大降AI工具,毕业论文AIGC率断崖降至5%以内!
  • 【Java原生互操作性能天花板突破】:实测对比JNI/FFM/JNR在高并发场景下吞吐量差异达4.7倍,附压测报告与选型决策矩阵
  • 【PlatformIO实战】ESP8266锂电池电量监测:从分压电路到OLED显示的完整方案
  • Flameshot设计系统解析:从原型迭代到交互规范的最佳实践
  • 当UNet遇上形态学:手把手解析MMUNet如何用腐蚀膨胀模块提升结肠癌分割边缘精度
  • 3分钟上手!零代码实现专业视频处理的ffmpegGUI全攻略
  • 大润发购物卡变现技巧:快速变现方法有哪些? - 团团收购物卡回收
  • 进阶篇01-频域滤波实战:Halcon中的功率谱分析与应用
  • ASMR音频下载完整指南:使用asmr-downloader轻松获取asmr.one海量资源
  • @giszhc/socket-client:前端web-socket通讯神器,这才是更优解(附在线示例)
  • 告别Keil调试:用Trace32模拟器离线分析LiteOS的elf与dump文件(STM32L475实战)
  • 数模混合芯片中Calibre PEX提取Hspice Netlist的关键步骤与常见问题解析
  • 终极指南:3分钟快速部署开源AI文本检测工具GPTZero
  • 开源Cursor Free VIP工具:突破AI编程助手限制的终极方案
  • gdb调试集锦
  • OpenClaw人人养虾:Deepgram 语音转写
  • 别再只盯着CT了!5分钟搞懂MRI水成像和化学位移成像,医生是怎么看清你身体里那些“水”的
  • OpenClaw监控技能:用SecGPT-14B实现24/7网络异常检测
  • Win11Debloat完整指南:如何一键清理Windows系统,提升51%性能的免费神器
  • 电源硬件设计实战:基于TPS63070的高效Buck-Boost变换器应用解析