1. 更深入認識 record 類別
在深入了解 record patterns 之前,先複習一下什麼是 record 類別。
Record 是一種精簡且不可變的類別,會自動產生 equals、hashCode、toString 等方法,並為所有元件自動建立 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 類別的值:無論在 instanceof 或 switch 裡都可以。這讓程式碼更短、更安全也更容易理解。
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) — 同時檢查型別並立即將元件解構成變數 x 與 y。
- 變數 x 和 y 只在 if 區塊內可用。
結果:
x=10, y=20
注意:如果 obj 不是 Point 或者是 null,if 區塊不會執行,也不會宣告這些變數。
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) 中,我們立即取得 x 與 y。
- 在 case Circle(Point center, int r) 中可使用 center 與 r。
結果:
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 中宣告的名稱,只在成功比對的 if 或 case 的主體內可見。
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 的模式比對 |
|---|---|---|
| 型別檢查 | |
|
| 型別轉型 | |
不需要 |
| 存取欄位 | |
|
| 巢狀解構 | 需要大量樣板程式 | 巢狀模式 |
| 在 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:過度巢狀的模式。 巢狀能力很強大,但不要不必要地複雜化:過深的結構會降低可讀性並提高出錯風險。
GO TO FULL VERSION