Java.util.Date 已死!請(qǐng)不要再使用
環(huán)境:SpringBoot3.4.2
1. 簡(jiǎn)介
在Java8之前,處理時(shí)間使用java.util.Date是最主流,最廣泛的方式。如下示例:
// 1. 獲取當(dāng)前時(shí)間
Date now = new Date();
System.out.println("當(dāng)前時(shí)間: " + now);
// 2. 創(chuàng)建特定時(shí)間(已廢棄的構(gòu)造方法,但仍有使用)
@SuppressWarnings("deprecation")
Date specificDate = new Date(2025 - 1900, 11, 25); // 年份從1900開始計(jì)算,月份0-11
System.out.println("特定日期: " + specificDate); // 輸出可能不符合預(yù)期(時(shí)區(qū)問題)
// 3. 比較日期(使用 before()/after())
Date earlier = new Date(System.currentTimeMillis() - 1000);
System.out.println("now 是否在 earlier 之后? " + now.after(earlier));輸出結(jié)果
當(dāng)前時(shí)間: Mon Aug 18 07:37:08 CST 2025
特定日期: Thu Dec 25 00:00:00 CST 2025
now 是否在 earlier 之后? true問題:
- Date 的構(gòu)造方法 Date(int year, int month, int day) 已廢棄(年份需 -1900,月份 0-11)
 - 輸出格式不友好(如 Mon Dec 25 00:00:00 CST 2023),需配合 SimpleDateFormat 格式化
 
示例2:
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
// 1. 格式化 Date 為字符串
Date now = new Date();
String formattedDate = sdf.format(now);
System.out.println("格式化后: " + formattedDate);
// 2. 解析字符串為 Date
String dateStr = "2025-11-25 12:00:00";
Date parsedDate = sdf.parse(dateStr);
System.out.println("解析后: " + parsedDate);輸出結(jié)果
格式化后: 2025-08-17 07:38:10
解析后: Tue Nov 25 12:00:00 CST 2025問題:
- SimpleDateFormat 不是線程安全的(多線程環(huán)境下需額外處理)
 - 時(shí)區(qū)處理復(fù)雜(需手動(dòng)設(shè)置 setTimeZone())
 
但多年過去,我漸漸認(rèn)識(shí)到——有時(shí)還是吃了苦頭才明白——這個(gè)類在設(shè)計(jì)上存在諸多陷阱,會(huì)讓你的代碼變得脆弱且難以理解。
好在,Java也在不斷進(jìn)化。
如今,我們有了更優(yōu)秀、更安全、更簡(jiǎn)潔,用起來也更加得心應(yīng)手的替代方案。
在本文中,我將先為你剖析為何不推薦使用Date類,接著通過實(shí)際的代碼示例,為你展示一些現(xiàn)代的解決方案。
2. 為什么避免使用Date
2.1 名稱具有誤導(dǎo)性
盡管名為“Date”(日期),但它并不只是表示一個(gè)日期(如 2025 年 8 月 17 日)。
相反,它表示的是一個(gè)時(shí)間戳——即自 1970 年 1 月 1 日(紀(jì)元)起以毫秒為單位計(jì)量的某個(gè)特定時(shí)刻。如下示例:
Date now = new Date();
System.out.println("當(dāng)前時(shí)間: " + now);
// 輸出結(jié)果:當(dāng)前時(shí)間: Mon Aug 17 07:43:41 CST 2025可讀性差,格式不直觀;底層以毫秒數(shù)存時(shí)間戳,它只是一個(gè)長(zhǎng)值(date.getTime()),而不是 "日歷日期"。
替代方案:
如果你要的是不帶時(shí)間的日歷日期,請(qǐng)使用 LocalDate。
LocalDate today = LocalDate.now();
System.out.println(today);輸出結(jié)果
2025-08-17干凈、清晰,沒有混淆。
2.2 可變性問題
Date 對(duì)象是可變的。
你可以更改其內(nèi)部值,這使得它們?cè)诠蚕頃r(shí)存在安全隱患,并迫使采用防御性復(fù)制。
Date date = new Date() ;
date.setTime(0L) ; 
System.err.println(date) ;輸出結(jié)果
Thu Jan 01 08:00:00 CST 1970是不是驚呆了,輸出1970。
如果其他代碼引用了你的 Date 對(duì)象,它就會(huì)背著你把它弄亂。
這就是產(chǎn)生 bug 的原因。
替代方案:
java.time 包中的所有類都是不可變和線程安全的。
LocalDateTime now = LocalDateTime.now() ;
LocalDateTime changed = now.plusDays(5) ;
System.out.println(now) ;
System.out.println(changed) ;輸出結(jié)果
2025-08-17T07:54:25.775775700
2025-08-22T07:54:25.775775700請(qǐng)注意,plusDays(5) 并沒有修改原始的 now,而是返回一個(gè)新的實(shí)例。
2.3 糟糕的 API 設(shè)計(jì)
如果你用過 getYear()、getMonth() 或 getDay() 等方法,你就會(huì)知道 Date 是多么痛苦:
Date date = new Date(); 
// 返回的是自1900年以來的年數(shù)(125)
System.out.println(date.getYear()); 
// 月份從0開始計(jì)數(shù)(0代表一月)(7)
System.out.println(date.getMonth()); 
// 返回星期幾(0代表星期日)
System.out.println(date.getDay());并且上面3個(gè)方法都已經(jīng)標(biāo)記為廢棄。
替代方案:
使用具有簡(jiǎn)潔流暢 API 的 LocalDateTime 或 ZonedDateTime。
LocalDateTime now = LocalDateTime.now();
System.out.println(now.getYear());
System.out.println(now.getMonth());
System.out.println(now.getDayOfWeek());輸出結(jié)果
2025
AUGUST
MONDAY2.4 時(shí)區(qū)模糊
Date#toString() 使用系統(tǒng)默認(rèn)時(shí)區(qū)。
這意味著同一個(gè) Date 對(duì)象在不同的機(jī)器上可能顯示不同的時(shí)區(qū)。
圖片
想象一下,你在調(diào)試一個(gè)生產(chǎn)環(huán)境中的故障:不同地區(qū)的服務(wù)器對(duì)同一時(shí)刻記錄的時(shí)間卻各不相同。
替代方案:
使用 java8 Instant 表示機(jī)器時(shí)間,或使用 ZonedDateTime 表示人類友好時(shí)間。
Instant instant = Instant.ofEpochMilli(0);
System.out.println(instant); 
// 帶時(shí)區(qū)的可讀格式
ZonedDateTime zoned = instant.atZone(ZoneId.of("Asia/Shanghai"));
System.out.println(zoned);輸出結(jié)果
1970-01-01T00:00:00Z
1970-01-01T08:00+08:00[Asia/Shanghai]2.5 子類化的怪異現(xiàn)象
你是否遇到過 java.sql.Date 或 java.sql.Timestamp?
這些 java.util.Date 的子類對(duì)方法的重寫方式可能會(huì)讓你大吃一驚。
這是典型的反面教材,展示了繼承為何可能帶來風(fēng)險(xiǎn)。
替代方案:
JDBC 現(xiàn)在可直接使用 java.time 類,如 LocalDate、LocalTime、Instant 等。無需使用古怪的子類。
Connection conn = null ;
PreparedStatement ps = conn.prepareStatement("INSERT INTO users (dob) VALUES (?)");
ps.setObject(1, LocalDate.of(1993, 5, 15));
ps.executeUpdate();這種方式更清晰,且能避免意外的類型不匹配問題。
3. 現(xiàn)代替換方案
那么,除了 java.util.Date,你應(yīng)該使用什么呢?讓我們逐一分析常見場(chǎng)景:
3.1 當(dāng)你需要當(dāng)前時(shí)刻(機(jī)器時(shí)間)時(shí)
舊方式
Date now = new Date() ;新方式
Instant now = Instant.now() ;Instant 非常使用表示時(shí)間戳,精確,不可變且使用使用UTC時(shí)區(qū)。
3.2 當(dāng)你需要一個(gè)不帶時(shí)間的日期日期時(shí)
舊方式:你最終還是會(huì)得到一個(gè)完整的Date對(duì)象,然后手動(dòng)去除時(shí)間部分。
新方式
LocalDate today = LocalDate.nwo() ;
System.out.println(today) ; // 2025-08-17LocalDate 值提供日期。
3.3 當(dāng)你需要一個(gè)不帶日期的時(shí)間點(diǎn)時(shí)
Date無法很好地表示這種情況。
新方式:
LocalTime now = LocalTime.now() ;
System.out.println(now) ; // 08:30:22.146非常適合表示 那種具體時(shí)間點(diǎn)的場(chǎng)景。
3.4 當(dāng)你需要日期和時(shí)間,但不需要時(shí)區(qū)時(shí)
舊方式:Date會(huì)提供一個(gè)時(shí)間戳,但其表示形式取決于系統(tǒng)的默認(rèn)設(shè)置。
新方式:
LocalDateTime meeting = LocalDateTime.of(2025, 8, 19, 14, 30) ;
System.out.println(meeting) ; // 2025-08-19T14:30LocalDateTime明確且易于閱讀。
3.5 當(dāng)你需要日期,時(shí)間和時(shí)區(qū)時(shí)
舊方式:使用 Date + Calendar 極其繁瑣。
// 1. 創(chuàng)建 Calendar 實(shí)例(需指定時(shí)區(qū))
Calendar calendar = Calendar.getInstance(TimeZone.getTimeZone("Asia/Shanghai"));
// 2. 設(shè)置日期和時(shí)間(手動(dòng)拆分年/月/日等)
calendar.set(2025, Calendar.AUGUST, 17, 14, 30, 0); // 注意:月份從0開始(8月=7)
calendar.set(Calendar.MILLISECOND, 0); // 清除毫秒(可選)
// 3. 獲取 Date 對(duì)象(僅時(shí)間戳,無時(shí)區(qū)信息)
Date oldDate = calendar.getTime();
// 4. 輸出時(shí)需手動(dòng)格式化(且無法直接顯示時(shí)區(qū))
System.out.println("舊方式結(jié)果: " + oldDate); 
// 5. 若需時(shí)區(qū)信息,需額外處理...
TimeZone timeZone = calendar.getTimeZone();
System.out.println("時(shí)區(qū): " + timeZone.getDisplayName()); // 輸出:中國(guó)標(biāo)準(zhǔn)時(shí)間輸出結(jié)果
舊方式結(jié)果: Sun Aug 17 14:30:00 CST 2025
時(shí)區(qū): 中國(guó)標(biāo)準(zhǔn)時(shí)間新方式:
ZonedDateTime zdt = ZonedDateTime.now(ZoneId.of("UTC+8")) ;
// 2025-08-17T08:32:13.666470100+08:00[UTC+08:00]
System.out.println(zdt) ;ZonedDateTime 使時(shí)區(qū)處理變得明確,避免了隱藏的問題。
3.6 當(dāng)你需要時(shí)間段或周期時(shí)
舊方式:你需要手動(dòng)計(jì)算兩個(gè) Date 之間的毫秒數(shù)。
新方式:
LocalDate start = LocalDate.of(2025, 1, 1) ;
LocalDate end = LocalDate.of(2025, 8, 17) ;
Period period = Period.between(start, end) ;
System.out.println(period) ;輸出結(jié)果
P7M16D (7個(gè)月16天)計(jì)算精確的時(shí)間差:
Instant start = Instant.now() ;
TimeUnit.MILLISECONDS.sleep(3000) ;
Instant end = Instant.now() ;
Duration duration = Duration.between(start, end) ;
System.err.println(duration + " ms") ;輸出結(jié)果
PT3.0138012S ms3.7 從Date遷移到j(luò)ava.time
你經(jīng)常會(huì)遇到仍在使用 Date 的遺留API。
轉(zhuǎn)換很容易:
// Date -> Instant
Date d = new Date();
Instant instant = d.toInstant() ;
// Instant -> Date
Date dd = Date.from(instant) ;














 
 
 







 
 
 
 