一、 現代Java演進主軸:代數資料型別與資料導向編程(Data-Oriented Programming)
在傳統Java中,物件導向往往將資料與行為緊密包裹在同一個臃腫的類別中,並伴隨大量樣板程式碼(Boilerplate Code,如 Getter/Setter/equals/hashCode)。
從Java 14到Java 21,Java官方透過Project Amber引入了一整套連貫的語法矩陣:
- Record(乘積型別/Product Type):宣告資料的透明載體。
- Sealed Classes(和型別/Sum Type):嚴格限制型別階層的可能子類。
- Pattern Matching(模式匹配):優雅拆解與消費上述資料結構。
三者結合即在Java中完整實現了代數資料型別(Algebraic Data Types, ADT),使型別檢查在編譯期達成百分之百的窮舉與安全。
二、 record:透明、純粹且不可變的資料載體
record是Java16正式引入的特化類別,專門用於取代傳統的DTO、VO或API請求/回應物件。
- 底層真實樣貌與語義約束
當宣告public record UserDto(Long id, String name) {} 時,編譯器在底層自動完成了以下規範:
- 預設隱式繼承java.lang.Record:因此record不能再繼承任何類別,但依然允許實作介面。
- 類別自身隱式標記為final:不可被其他類別繼承。
- 成員變數皆為 private final:天然保證淺層不可變性(Shallow Immutability)。
- 存取器命名:不生成 getId(),而是直接生成與欄位同名的存取方法 id() 與 name()。
- 自動實作經典契約:編譯器基於所有宣告元件自動生成嚴格對齊的 equals()、hashCode() 與 toString()。
- 精簡建構子的最佳實踐
Record提供了緊湊建構子語法,專門用於參數校驗(Validation)與正規化(Normalization),不需要重複寫宣告this.x = x:
public record OrderRequest(String orderId, int amount) {
// 緊湊建構子:省略參數括號,JVM 會在最後自動隱式賦值
public OrderRequest {
if (orderId == null || orderId.isBlank()) {
throw new IllegalArgumentException("訂單編號不可為空");
}
if (amount <= 0) {
throw new IllegalArgumentException("金額必須大於 0");
}
orderId = orderId.trim(); // 正規化處理
}
}
三、 sealed密封類別與介面:可控的繼承樹
傳統Java中,類別要嘛是開放被所有人隨意繼承,要嘛加上final徹底禁止繼承,缺乏折衷的中間層。
Java17引入的Sealed Classes允許類別作者精確指定只有哪些子類別能夠繼承我。
- 語法結構與規則
// 宣告抽象密封介面,白名單限定只有以下三個具體類別可實作
public sealed interface PaymentStatus
permits PaymentSuccess, PaymentFailed, PaymentPending {}
// 子類別必須顯式宣告以下三種修飾詞之一:
public final class PaymentSuccess implements PaymentStatus { ... } // 1. final:封死,不可再繼承
public sealed class PaymentPending implements PaymentStatus permits ... {} // 2. sealed:繼續指定下層白名單
public non-sealed class PaymentFailed implements PaymentStatus { ... } // 3. non-sealed:打破封印,開放繼承
- 同套件 / 同模組限制:permits 指定的子類別必須與sealed類別位於同一個 Package或同一個Named Module中。若子類別定義在同一個.java原始碼檔案中,permits 關鍵字甚至可以省略。
四、 現代Pattern Matching完整演進與Switch窮舉
Java21正式敲定了Pattern Matching for switch與Record Patterns,徹底淘汰了冗長脆弱的if-else + instanceof鏈條。
- Record 解構模式(Deconstruction Pattern)
可以在模式匹配的同時,直接將 record 物件內部的欄位解構提取為區域變數,甚至支援巢狀解構:
public record Point(int x, int y) {}
public record Circle(Point center, int radius) {}
public void printCenter(Object shape) {
// 巢狀解構:直接深入 Circle 內部抓出 Point 的 x 與 y
if (shape instanceof Circle(Point(int x, int y), int r)) {
System.out.printf("圓心座標:(%d, %d),半徑:%d%n", x, y, r);
}
}
- Switch模式匹配與編譯期窮舉
當switch搭配seale類別或列舉使用時,若已涵蓋所有合法子型別,編譯器允許完全省略default分支!
public String handlePayment(PaymentStatus status) {
// 當 PaymentStatus 為 sealed 介面時,編譯器能自動驗證完整性
return switch (status) {
case PaymentSuccess s -> "付款成功,交易序號: " + s.txId();
case PaymentFailed f when f.isRetryable() -> "付款失敗(可重試),原因: " + f.reason(); // when 保護條件(Guard)
case PaymentFailed f -> "付款失敗(不可重試),轉人工處理";
case PaymentPending p -> "交易排隊處理中";
// 由於子類已被封閉窮舉,編譯器不需要 default!
// 未來若有人在 PaymentStatus 新增一個子類,這裡在編譯期會立即報錯提醒補齊分支!
};
}
- 保護條件:取代了以往在case區塊內部寫巢if的混亂語法,讓條件判斷在宣告時一目了然。
- Null安全處理:在現代switch中,允許顯式宣告case null -> ...,擺脫過去 switch遇null必定無預警拋出NPE的歷史包袱。