如果你是一名 Java 开发者,java.util.Date 这个类你一定见过。
甚至可以说—— 你没主动用过,也一定被它“坑”过。
很多新同学都会有个疑问:
明明是 JDK 自带的类, 为什么老项目里一堆
Date, 但新项目却几乎看不到它了?
答案很简单一句话:
它太老了,老到不适合现代开发。
今天我们就用最直观的代码 + 最真实的坑,把这个“Java 的老古董”一次性讲清楚。
-****01-
**一个“活化石”级别的类 **
java.util.Date 诞生于 Java 1.0(1996 年)。
那一年:
Java 还没泛型
还没注解
甚至连集合框架都不完整
你现在看到的 Date,本质是 30 年前的设计产物。
<pre data-start="539" data-end="600" style="-webkit-tap-highlight-color: rgba(0, 0, 0, 0); margin: 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important;">
Date date = newDate(); System.out.println(date);
</pre>
输出:
<pre data-start="607" data-end="643" style="-webkit-tap-highlight-color: rgba(0, 0, 0, 0); margin: 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important;">
MonSep2209:50:24CST2025
</pre>
看起来是不是很“正常”? 坑,正是从这里开始的。
[图片上传失败...(image-c3b22e-1767692579858)]
-****02-
**Date 的三大“反人类”设计缺陷 **
1. API 设计:
<pre data-start="731" data-end="888" style="-webkit-tap-highlight-color: rgba(0, 0, 0, 0); margin: 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important;">
Date date = newDate(); System.out.println("当前年月日:" + LocalDate.now()); System.out.println(date.getYear()); System.out.println(date.getMonth());
</pre>
输出:
<pre data-start="895" data-end="925" style="-webkit-tap-highlight-color: rgba(0, 0, 0, 0); margin: 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important;">
当前年月日:2025-09-22 125 8
</pre>
问题来了:
getYear()返回 125?getMonth()返回 8?
原因是:
年份:从 1900 年开始算****
月份:从 0 开始(0 = 一月)****
2. 可变对象:
<pre data-start="1103" data-end="1274" style="-webkit-tap-highlight-color: rgba(0, 0, 0, 0); margin: 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important;">
Date date = newDate(2025 - 1900, 8, 22); System.out.println("原定日期: " + date); // 某个地方悄悄改了它 date.setYear(2026 - 1900); System.out.println("修改后的日期: " + date);
</pre>
输出:
<pre data-start="1281" data-end="1360" style="-webkit-tap-highlight-color: rgba(0, 0, 0, 0); margin: 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important;">
原定日期: Mon Sep 22 00:00:00 CST 2025 修改后的日期: Tue Sep 22 00:00:00 CST 2026
</pre>
在多线程环境下:
谁改了?
什么时候改的?
为什么变了?
debug 到秃头都找不到[哭]
3. 时区语义混乱
<pre data-start="1464" data-end="1523" style="-webkit-tap-highlight-color: rgba(0, 0, 0, 0); margin: 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important;">
Date now = newDate(); System.out.println(now);
</pre>
你看到的是:
- 系统默认时区的展示结果
但 Date 本身:
****只存一个毫秒时间戳****
****根本不关心时区****
展示和真实语义是分裂的,非常容易踩坑。
[图片上传失败...(image-78405f-1767692579858)]
-****03-
一个真实业务场景:算天数差
用 Date 算两个日期差几天,你只能这么干:
<pre data-start="1669" data-end="1908" style="-webkit-tap-highlight-color: rgba(0, 0, 0, 0); margin: 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important;">
Date date1 = newDate(125, 8, 22); // 2025-09-22 Date date2 = newDate(125, 9, 22); // 2025-10-22 long diff = date2.getTime() - date1.getTime(); long days = diff / (1000 * 60 * 60 * 24); System.out.println("相差天数: " + days);
</pre>
问题是:
可读性差
全是魔法数字
时区 / 夏令时一变就翻车
[图片上传失败...(image-9fc0d7-1767692579858)]
-****04-
最后的最后
替代方案:Java 8 时间 API(java.time)
Java 8 之后,官方已经给出了“正确答案”。
1. API 清晰到像自然语言
<pre data-start="2045" data-end="2126" style="-webkit-tap-highlight-color: rgba(0, 0, 0, 0); margin: 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important;">
LocalDate date = LocalDate.of(2025, 9, 22); System.out.println(date);
</pre>
<pre data-start="2128" data-end="2180" style="-webkit-tap-highlight-color: rgba(0, 0, 0, 0); margin: 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important;">
LocalDateTime now = LocalDateTime.now();
</pre>
<pre data-start="2182" data-end="2269" style="-webkit-tap-highlight-color: rgba(0, 0, 0, 0); margin: 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important;">
ZonedDateTime shanghai = ZonedDateTime.now(ZoneId.of("Asia/Shanghai"));
</pre>
2. 不可变对象,天生线程安全
<pre data-start="2319" data-end="2427" style="-webkit-tap-highlight-color: rgba(0, 0, 0, 0); margin: 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important;">
LocalDate appointment = LocalDate.of(2025, 9, 22); LocalDate newDate = appointment.plusDays(30);
</pre>
原对象不变
返回新对象
并发场景天然安全
3. 时间计算一行搞定
<pre data-start="2480" data-end="2593" style="-webkit-tap-highlight-color: rgba(0, 0, 0, 0); margin: 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important;">
long days = ChronoUnit.DAYS.between( LocalDate.of(2025, 9, 22), LocalDate.of(2025, 10, 22) );
</pre>
这才是 “业务代码”该有的样子。
4. 时区是显式的,而不是“猜的”
<pre data-start="2649" data-end="2840" style="-webkit-tap-highlight-color: rgba(0, 0, 0, 0); margin: 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important;">
ZonedDateTime shanghai= ZonedDateTime.now(ZoneId.of("Asia/Shanghai")); ZonedDateTime newYork= shanghai.withZoneSameInstant( ZoneId.of("America/New_York") );
</pre>
[图片上传失败...(image-697e8-1767692579857)]
老项目怎么办?
不用你“一刀切重构”
推荐做法:
新代码:一律用
java.time旧代码交互:只在边界转换
<pre data-start="2947" data-end="3166" style="-webkit-tap-highlight-color: rgba(0, 0, 0, 0); margin: 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important;">
public static LocalDate toLocalDate(Date date) { if (date == null) { return null; } return date.toInstant() .atZone(ZoneId.systemDefault()) .toLocalDate(); }
</pre>
<pre data-start="3168" data-end="3399" style="-webkit-tap-highlight-color: rgba(0, 0, 0, 0); margin: 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important;">
publicstatic LocalDateTime toLocalDateTime(Date date) { if (date == null) { return null; } return date.toInstant() .atZone(ZoneId.systemDefault()) .toLocalDateTime(); }
[图片上传失败...(image-64c285-1767692579856)]
</pre>
(面试直接背)
java.util.Date是历史产物, 可变、反直觉、时区混乱、不适合现代并发系统。Java 8 之后,
java.time才是唯一正确选择。
如果你在项目里还大量看到 Date,
那不是你的错—— 但你有责任在新代码里终结它。