⭐⭐⭐ Spring Boot 项目实战 ⭐⭐⭐ Spring Cloud 项目实战
《Dubbo 实现原理与源码解析 —— 精品合集》 《Netty 实现原理与源码解析 —— 精品合集》
《Spring 实现原理与源码解析 —— 精品合集》 《MyBatis 实现原理与源码解析 —— 精品合集》
《Spring MVC 实现原理与源码解析 —— 精品合集》 《数据库实体设计合集》
《Spring Boot 实现原理与源码解析 —— 精品合集》 《Java 面试题 + Java 学习指南》

摘要: 原创出处 juejin.cn/post/7552824412115812403 「四七伵」欢迎转载,保留摘要,谢谢!


🙂🙂🙂关注微信公众号:【芋道源码】有福利:

  1. RocketMQ / MyCAT / Sharding-JDBC 所有源码分析文章列表
  2. RocketMQ / MyCAT / Sharding-JDBC 中文注释源码 GitHub 地址
  3. 您对于源码的疑问每条留言都将得到认真回复。甚至不知道如何读源码也可以请教噢。
  4. 新的源码解析文章实时收到通知。每周更新一篇左右。
  5. 认真的源码交流微信群。

前言

如果你是一名 Java 开发者,很可能在日常工作中经常见到 java.util.Date 类。但你可能也注意到,现在 Java 开发中,我们通常会避免直接使用它。为什么会这样呢?今天我们就来聊聊这个话题。

一、一个“历史悠久”的类

java.util.Date 自 Java 1.0 (1996年) 就存在了(可能比在座的各位出生的还早),可以说它是 Java 标准库中的”元老”了。但正如许多古老的事物一样,它带着当时设计的局限性:

Date date = new Date();
System.out.println(date);

控制台输出结果

Mon Sep 22 09:50:24 CST 2025

看起来很简单对吧?但问题就藏在这简单背后。

二、设计缺陷一览

1. 令人困惑的 API 设计

Date 类的许多方法都已经过时(deprecated),而且设计上存在很多不合理之处:

Date date = new Date(); 
System.out.println("当前年月日:" + LocalDate.now());
System.out.println(date.getYear());
System.out.println(date.getMonth());

控制台输出结果

当前年月日:2025-09-22
125
8

看到问题了吗?年份从 1900 年开始计算,月份从 0 开始(0 表示一月,11 表示十二月)。这种反直觉的设计很容易导致错误。

2. 可变性带来的问题

Date 对象是可变的(mutable),这意味着你创建了一个 Date 对象后,它的值还可以被改变:

Date date = new Date(2025 - 1900, 8, 22);
System.out.println("原定日期: " + date);
// 某人意外地修改了这个日期的年份
date.setYear(2026 - 1900);
System.out.println("修改后的日期: " + date);

控制台输出结果

原定日期: Mon Sep 22 00:00:00 CST 2025
修改后的日期: Tue Sep 22 00:00:00 CST 2026

这种可变性在多线程环境下尤其危险,容易导致难以调试的并发问题。

3. 时区处理困难

Date 实际上并不存储时区信息,它只是自 1970年1月1日00:00:00 GMT 以来的毫秒数。但它的 toString() 方法却使用系统默认时区来显示时间,这很容易造成混淆:

Date now = new Date();
System.out.println(now); // 输出取决于你的默认时区

4. 精度限制

Date 只能精确到毫秒级别,对于需要更高精度(如微秒、纳秒)的应用场景无法满足需求。

三、一个实际案例

假设我们要计算两个日期之间的天数差,使用 Date 会非常麻烦:

// 使用 Date 计算两个日期相差的天数(不推荐的方式)
Date date1 = new Date(125, 8, 22); // 2025年9月22日
Date date2 = new Date(125, 9, 22); // 2025年10月22日

long difference = date2.getTime() - date1.getTime();
long daysBetween = difference / (1000 * 60 * 60 * 24);
System.out.println("相差天数: " + daysBetween);

控制台输出结果

相差天数: 30

这段代码不仅难以阅读,还需要手动处理毫秒转换,容易出错。

四、更好的替代方案:Java 8 时间 API

自从 Java 8 (2014年) 引入了 java.time 包,我们有了现代、完善的时间日期处理 API。

1. 清晰的 API 设计

// 创建指定日期
LocalDate date = LocalDate.of(2025, 9, 22); // 2025年9月22日,直观明了!
System.out.println(date);
// 获取当前日期时间
LocalDateTime now = LocalDateTime.now();
System.out.println(now);
// 获取当前日期时间,带时区
ZonedDateTime zonedNow = ZonedDateTime.now(ZoneId.of("Asia/Shanghai"));
System.out.println(zonedNow);

控制台输出结果

2025-09-22
2025-09-22T10:30:21.743
2025-09-22T10:30:21.744+08:00[Asia/Shanghai]

2. 不可变性,线程安全

LocalDate appointment = LocalDate.of(2025, 9, 22);
// 下面的操作会返回新的对象,原对象不变
LocalDate newDate = appointment.plusDays(30);

3. 强大的计算功能

// 计算两个日期之间的天数
LocalDate date1 = LocalDate.of(2025, 9, 22);
LocalDate date2 = LocalDate.of(2025, 10, 22);

long daysBetween = ChronoUnit.DAYS.between(date1, date2);
System.out.println("相差天数: " + daysBetween);

控制台输出结果

相差天数: 30

4. 完善的时区支持

// 明确处理时区
ZonedDateTime shanghaiTime = ZonedDateTime.now(ZoneId.of("Asia/Shanghai"));
ZonedDateTime newYorkTime = shanghaiTime.withZoneSameInstant(ZoneId.of("America/New_York"));

五、迁移建议

通常不建议,请慎重选择。

如果你在维护老项目,看到大量的 Date 代码,可以考虑以下迁移策略:

  • 新代码: 一律使用 java.time 包中的类
  • 与旧代码交互: 使用新增的转换方法:
/**
* 将 java.util.Date 转换为 LocalDate
* @param date java.util.Date
* @return LocalDate(仅包含年月日),如果 date 为 null 返回 null
*/
public static LocalDate toLocalDate(Date date) {
if (Objects.isNull(date)) {
returnnull;
}
return date.toInstant()
.atZone(ZoneId.systemDefault())
.toLocalDate();
}

/**
* 将 java.util.Date 转换为 LocalDateTime
* @param date java.util.Date
* @return LocalDateTime(包含年月日 + 时分秒),如果 date 为 null 返回 null
*/
public static LocalDateTime toLocalDateTime(Date date) {
if (Objects.isNull(date)) {
returnnull;
}
return date.toInstant()
.atZone(ZoneId.systemDefault())
.toLocalDateTime();
}

六、总结

java.util.Date 就像是编程世界中的”古董”——它有历史价值,但在现代开发中有更优的API可以替换。它的设计缺陷、线程安全问题以及难以使用的 API 都让我们有充分的理由转向更现代的 java.time API。

希望这篇文章能帮助你理解为什么不推荐使用 java.util.Date,以及如何在你的项目中使用更好的替代方案。

文章目录
  1. 1. 前言
  2. 2. 一、一个“历史悠久”的类
  3. 3. 二、设计缺陷一览
    1. 3.0.1. 1. 令人困惑的 API 设计
    2. 3.0.2. 2. 可变性带来的问题
    3. 3.0.3. 3. 时区处理困难
    4. 3.0.4. 4. 精度限制
  • 4. 三、一个实际案例
  • 5. 四、更好的替代方案:Java 8 时间 API
    1. 5.0.1. 1. 清晰的 API 设计
    2. 5.0.2. 2. 不可变性,线程安全
    3. 5.0.3. 3. 强大的计算功能
    4. 5.0.4. 4. 完善的时区支持
  • 6. 五、迁移建议
  • 7. 六、总结