CodeGym /課程 /JAVA 25 SELF /java.time API 概觀,與舊 API 的差異

java.time API 概觀,與舊 API 的差異

JAVA 25 SELF
等級 13 , 課堂 1
開放

1. 歷史背景:在 Java 8 之前如何處理日期

在遙遠的過去(Java 8 之前),處理日期與時間會使用 java.util.Datejava.util.Calendar,以及用於格式化的 java.text.SimpleDateFormat。這就是那種情況,讓全世界的程式設計師一起嘆氣,咬著牙寫出類似下面的程式碼:

import java.util.Date;

Date now = new Date();
System.out.println(now); // 會輸出一些奇怪的東西,例如 "Wed Jun 05 14:15:22 MSK 2025"

聽起來很簡單,但實際上並不美好。以下只是舊 API 的一些「樂趣」:

  • Date 是可變(mutable)的物件。很容易被不小心改動,經常導致 bug。
  • DateCalendar 的月份從 0 開始(1 月是 0,12 月是 11),而天數從 1 開始。
  • SimpleDateFormat 非執行緒安全:若兩個執行緒同時格式化日期,可能得到不可預期的結果。
  • 大量方法被標記為 @Deprecated(已淘汰),而 IDE 不斷用黃色警告嚇你。
  • 處理時區簡直是場噩夢:很容易把本地時間與 UTC 混淆,更別提夏令/冬令時間的切換。

痛點示例

import java.util.Date;

Date date = new Date(2025, 5, 1); // 年:2025,月:5(六月?),日:1
System.out.println(date); // 跟你想的不一樣!

2. 出現 java.time:全新思路

到了 2014 年,人們已經明白:舊的 API 不只是不好用——還有風險。因此 Java 引入了新的套件 —— java.time,實作 JSR‑310 規範。這個 API 受到熱門函式庫 Joda-Time 的啟發,並迅速成為事實上的標準。

主要套件與類別

  • java.time —— 主要套件,新的日期與時間類別都在這裡。
  • java.time.format —— 用於日期與時間的格式化與解析。
  • java.time.temporal —— 進階的時間運算。
  • java.time.zone —— 處理時區。

以下是新 API 的主角:

類別 用途 使用範例
LocalDate
只有日期(年、月、日) 生日,不含時間
LocalTime
只有時間(時、分、秒) 會議時間,不含日期
LocalDateTime
日期與時間,不含時區 本地事件
ZonedDateTime
日期與時間,含時區 以明斯克當地時間開會
Instant
絕對時間點(UTC) 日誌事件標記
Duration
時間間隔(時、分、秒) 通話時長
Period
期間(年、月、日) 工作年資、年齡

範例:用新方式建立日期

import java.time.LocalDate;

LocalDate today = LocalDate.now();
System.out.println(today); // 例如 "2025-06-05"

3. 新 API 的優勢

不可變(immutable)

java.time 中的所有類別都是不可變的。也就是說:如果你建立了一個 LocalDate 物件,就不能修改它。任何操作(例如加一天)都會回傳新的物件。

LocalDate today = LocalDate.now();
LocalDate tomorrow = today.plusDays(1);

System.out.println(today);    // 2025-06-05
System.out.println(tomorrow); // 2025-06-06

明確處理時區

在舊 API 中很容易忘記日期位於哪個時區。在 java.time 中一切都是明確的:如果需要時區 —— 使用 ZonedDateTime;如果不需要 —— 使用 LocalDateTime

import java.time.ZonedDateTime;
import java.time.ZoneId;

ZonedDateTime MinskTime = ZonedDateTime.now(ZoneId.of("Europe/Minsk"));
System.out.println(MinskTime); // 2025-06-05T14:23:45+03:00[Europe/Minsk]

便捷的運算與比較方法

LocalDate today = LocalDate.now();
LocalDate nextMonth = today.plusMonths(1);
boolean isAfter = LocalDate.now().plusDays(1).isAfter(today); // true

格式化與解析

格式化與解析 —— 透過 DateTimeFormatter(詳細內容在下一堂課):

import java.time.format.DateTimeFormatter;

LocalDate today = LocalDate.now();
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd.MM.yyyy");
String formatted = today.format(formatter); // "05.08.2025"

4. 相容性:如何與舊程式碼共存

在真實世界中,非常常見 需要與使用 DateCalendar 的函式庫或舊系統協作。所幸,新 API 對既有程式碼很友善:可以在新舊型別之間互相轉換。

轉換:DateInstant

import java.util.Date;
import java.time.Instant;

// Date → Instant
Date legacyDate = new Date();
Instant instant = legacyDate.toInstant();

// Instant → Date
Date dateBack = Date.from(instant);

轉換:CalendarZonedDateTime

import java.util.Calendar;
import java.time.ZonedDateTime;
import java.time.ZoneId;
import java.util.Date;

// Calendar → ZonedDateTime
Calendar calendar = Calendar.getInstance();
ZonedDateTime zdt = ZonedDateTime.ofInstant(
        calendar.toInstant(),
        calendar.getTimeZone().toZoneId()
);

// ZonedDateTime → Calendar
Calendar calBack = Calendar.getInstance();
calBack.setTime(Date.from(zdt.toInstant()));

表格:舊類別與新類別的對照

舊類別 新類別 備註
java.util.Date
Instant
絕對時間
java.util.Calendar
ZonedDateTime
含時區的日期與時間
java.text.SimpleDateFormat
DateTimeFormatter
日期的格式化/解析

5. 實作:初步上手 java.time

假設你有一位使用者的生日。來儲存並輸出這個日期:

import java.time.LocalDate;

public class UserProfile {
    private String name;
    private LocalDate birthDate;

    public UserProfile(String name, LocalDate birthDate) {
        this.name = name;
        this.birthDate = birthDate;
    }

    public void printProfile() {
        System.out.println("姓名: " + name);
        System.out.println("生日: " + birthDate);
    }
}

public class Main {
    public static void main(String[] args) {
        UserProfile user = new UserProfile("Alice", LocalDate.of(1998, 12, 25));
        user.printProfile();
    }
}

輸出:

姓名: Alice
生日: 1998-12-25

6. 比較:舊 API vs 新 API

範例:在生日上加一週

舊方法(Date/Calendar):

import java.util.Calendar;

Calendar cal = Calendar.getInstance();
cal.set(1998, Calendar.DECEMBER, 25);
cal.add(Calendar.WEEK_OF_YEAR, 1);
System.out.println(cal.getTime()); // 繁瑣且不直觀

新方法(java.time):

import java.time.LocalDate;

LocalDate birthDate = LocalDate.of(1998, 12, 25);
LocalDate nextWeek = birthDate.plusWeeks(1);
System.out.println(nextWeek); // 1999-01-01

在新 API 中,程式碼更短、更簡潔也更安全。

7. 使用 java.time 時的常見錯誤

錯誤 1:忘了物件是不可變的。
如果呼叫 date.plusDays(1); 但沒有保存回傳結果,原本的日期不會改變。

錯誤 2:混淆 LocalDateLocalDateTime
LocalDate 只保存日期(年、月、日),而 LocalDateTime 還包含時間。若需要處理時、分,別用錯型別。

錯誤 3:在新專案中使用舊類別。
只要有可能 —— 總是使用 java.time。舊類別只用於相容性需求。

錯誤 4:錯誤處理時區。
如果要保存對不同地區都重要的事件,請使用 ZonedDateTime,或至少用 InstantLocalDateTime 不包含任何時區資訊!

錯誤 5:嘗試直接比較 LocalDateLocalDateTime
它們是不同的型別,不能直接比較。先將其轉成同一型別再比較。

留言
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION