CodeGym /課程 /JAVA 25 SELF /Record Patterns (Java 21+): 語法、範例

Record Patterns (Java 21+): 語法、範例

JAVA 25 SELF
等級 65 , 課堂 3
開放

1. 更深入認識 record 類別

在深入了解 record patterns 之前,先複習一下什麼是 record 類別。

Record 是一種精簡且不可變的類別,會自動產生 equalshashCodetoString 等方法,並為所有元件自動建立 getter。Record 類別自 Java 16 引入,對於厭倦手動撰寫各種「DTO」的人來說,簡直是大禮。

// 經典寫法:具有兩個欄位的 Point
record Point(int x, int y) {}

建立物件與存取欄位:

Point p = new Point(10, 20);
System.out.println(p.x()); // 10
System.out.println(p.y()); // 20
System.out.println(p);     // Point[x=10, y=20]

為什麼需要 Record Patterns

在 Java 21 之前,如果你想檢查某個物件是否為某個 record,並取得它的欄位,通常需要寫不少樣板程式碼:透過 instanceof 檢查型別、轉型,然後呼叫 getter。

Object obj = new Point(5, 7);
if (obj instanceof Point) {
    Point p = (Point) obj;
    int x = p.x();
    int y = p.y();
    System.out.println("x=" + x + ", y=" + y);
}

如果 record 很複雜或是巢狀結構,程式很快就會變成一堆解構程式碼。過去要在 switch 中做這種辨識也不太方便。

Record patterns 允許你直接在模式比對中解構 record 類別的值:無論在 instanceofswitch 裡都可以。這讓程式碼更短、更安全也更容易理解。

2. record patterns 語法:最簡單的例子

record Point(int x, int y) {}

Object obj = new Point(10, 20);

if (obj instanceof Point(int x, int y)) {
    System.out.println("x=" + x + ", y=" + y);
}

這裡發生了什麼?

  • obj instanceof Point(int x, int y) — 同時檢查型別並立即將元件解構成變數 xy
  • 變數 xy 只在 if 區塊內可用。

結果:

x=10, y=20

注意:如果 obj 不是 Point 或者是 nullif 區塊不會執行,也不會宣告這些變數。

3. 在 switch 中使用 record patterns:簡潔又安全

record Point(int x, int y) {}
record Circle(Point center, int radius) {}

Object shape = new Point(3, 4);

switch (shape) {
    case Point(int x, int y) -> System.out.println("Point: x=" + x + ", y=" + y);
    case Circle(Point center, int r) -> System.out.println("Circle with radius " + r);
    default -> System.out.println("Unknown shape!");
}
  • case Point(int x, int y) 中,我們立即取得 xy
  • case Circle(Point center, int r) 中可使用 centerr

結果:

Point: x=3, y=4

搭配 sealed 階層時,switch 能做到窮舉:若遺漏某個分支,編譯器會提出警告。

4. 巢狀 record patterns:在解構中再解構

record Point(int x, int y) {}
record Line(Point start, Point end) {}

Object obj = new Line(new Point(1, 2), new Point(3, 4));

if (obj instanceof Line(Point(int x1, int y1), Point(int x2, int y2))) {
    System.out.println("Line from (" + x1 + "," + y1 + ") to (" + x2 + "," + y2 + ")");
}

結果:

Line from (1,2) to (3,4)

在處理樹、圖或複雜資料結構時,巢狀模式就是你的好朋友。

5. 帶守衛條件(when)的 record patterns

Object obj = new Point(0, 100);

if (obj instanceof Point(int x, int y) && x == 0) {
    System.out.println("點位於 Y 軸上:y=" + y);
}

在 switch 中可以搭配 when

switch (obj) {
    case Point(int x, int y) when x == 0 -> System.out.println("在 Y 軸上:y=" + y);
    case Point(int x, int y) when y == 0 -> System.out.println("在 X 軸上:x=" + x);
    case Point(int x, int y) -> System.out.println("一般的點");
    default -> System.out.println("不是點");
}

6. 在真實應用中的 record patterns

來看一個實用例子——幾何圖形。

record Point(int x, int y) {}
record Circle(Point center, int radius) {}
record Rectangle(Point topLeft, int width, int height) {}
public static void printShapeInfo(Object shape) {
    switch (shape) {
        case Point(int x, int y) -> System.out.println("Point: (" + x + ", " + y + ")");
        case Circle(Point(int x, int y), int radius) ->
            System.out.println("Circle: center=(" + x + ", " + y + "), radius=" + radius);
        case Rectangle(Point(int x, int y), int width, int height) ->
            System.out.println("Rectangle: topLeft=(" + x + ", " + y + "), size=" + width + "x" + height);
        default -> System.out.println("Unknown shape");
    }
}

呼叫範例:

printShapeInfo(new Rectangle(new Point(5, 10), 20, 30));

結果:

Rectangle: topLeft=(5, 10), size=20x30

7. 限制與使用細節

僅適用於 record 類別。 Record patterns 只對實際為 record 類別的物件有效。

class NotARecord {
    int a, b;
    NotARecord(int a, int b) { this.a = a; this.b = b; }
}

// 錯誤!這不是 record
// if (obj instanceof NotARecord(int a, int b)) { ... }

元件的數量與型別必須相符。 樣式必須對應到 record 類別的所有元件。

record Pair(int a, String b) {}

// 錯誤:型別不相符
// if (obj instanceof Pair(String a, int b)) { ... }

變數只在區塊內可見。 在 pattern 中宣告的名稱,只在成功比對的 ifcase 的主體內可見。

8. 巢狀模式:樹狀結構範例

sealed interface Expr permits NumberExpr, PlusExpr, MinusExpr {}

record NumberExpr(int value) implements Expr {}
record PlusExpr(Expr left, Expr right) implements Expr {}
record MinusExpr(Expr left, Expr right) implements Expr {}
int eval(Expr expr) {
    return switch (expr) {
        case NumberExpr(int value) -> value;
        case PlusExpr(Expr left, Expr right) -> eval(left) + eval(right);
        case MinusExpr(Expr left, Expr right) -> eval(left) - eval(right);
    };
}

使用範例:

Expr e = new PlusExpr(new NumberExpr(7), new MinusExpr(new NumberExpr(10), new NumberExpr(3)));
System.out.println(eval(e)); // 7 + (10 - 3) = 14

在這裡我們直接解構 NumberExpr(int value)PlusExpr(Expr left, Expr right)MinusExpr(Expr left, Expr right) —— 程式碼更精煉且更具表達力。

9. 表格:有無 record patterns 的模式比對比較

方式 手動轉型 使用 record patterns 的模式比對
型別檢查
if (obj instanceof ...)
if (obj instanceof Point(int x, int y))
型別轉型
(Point) obj
不需要
存取欄位
p.x(), p.y()
x, y
巢狀解構 需要大量樣板程式 巢狀模式
在 switch 中使用 不便/不可行 容易且精簡

10. 使用 record patterns 的常見錯誤

錯誤 1:試圖把 record pattern 用在非 record 類別上。 如果你寫的是一般類別,卻想把它「解構」成 record,編譯器不會允許。Record patterns 只適用於真正的 record 類別。

錯誤 2:元件的數量或型別不匹配。 樣式必須完整對應 record 的元件簽章。比如對於 record Pair(int a, String b),不能寫成 Pair(int x, int y)

錯誤 3:嘗試在區塊外使用 pattern 變數。 在樣式中宣告的名稱,只在對應的 if/case 區塊內可用。離開區塊後,這些變數就不存在。

錯誤 4:在較舊版本的 JDK 使用 record patterns。 支援自 Java 21 才有。在 Java 17 或更舊版本會產生語法錯誤。請檢查你的 JDK 版本與 IDE 支援情況。

錯誤 5:過度巢狀的模式。 巢狀能力很強大,但不要不必要地複雜化:過深的結構會降低可讀性並提高出錯風險。

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