Java Pattern Matching

Pattern Matching(模式匹配)是 Java 從 14 版開始逐步引入的功能,讓類型檢查和轉型更加簡潔。

instanceof Pattern Matching (Java 16+)

傳統寫法

// 舊寫法:需要額外的型別轉換
if (obj instanceof String) {
    String s = (String) obj;
    System.out.println(s.length());
}

Pattern Matching 寫法

// 新寫法:直接綁定變數
if (obj instanceof String s) {
    System.out.println(s.length());
}

// 可以在同一行使用
if (obj instanceof String s && s.length() > 5) {
    System.out.println("長字串: " + s);
}

作用域規則

// 變數在 pattern 匹配成功後的作用域內可用
if (obj instanceof String s) {
    // s 在這裡可用
    System.out.println(s);
}
// s 在這裡不可用

// 否定條件時,變數在 else 區塊可用
if (!(obj instanceof String s)) {
    return;
}
// s 在這裡可用(因為如果不是 String 就已經 return 了)
System.out.println(s.length());

Switch Pattern Matching (Java 21+)

類型模式

static String formatValue(Object obj) {
    return switch (obj) {
        case Integer i -> "整數: " + i;
        case Long l -> "長整數: " + l;
        case Double d -> "浮點數: " + d;
        case String s -> "字串: " + s;
        case null -> "空值";
        default -> "未知類型: " + obj.getClass();
    };
}

Guard 條件(when)

static String classify(Object obj) {
    return switch (obj) {
        case Integer i when i < 0 -> "負數";
        case Integer i when i == 0 -> "零";
        case Integer i when i > 0 -> "正數";
        case String s when s.isEmpty() -> "空字串";
        case String s when s.length() < 5 -> "短字串";
        case String s -> "長字串";
        case null -> "空值";
        default -> "其他";
    };
}

null 處理

static String process(String s) {
    return switch (s) {
        case null -> "空值";
        case String str when str.isEmpty() -> "空字串";
        case String str -> "內容: " + str;
    };
}

Record Pattern (Java 21+)

基本解構

record Point(int x, int y) {}

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

巢狀解構

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

static void printLine(Object obj) {
    if (obj instanceof Line(Point(int x1, int y1), Point(int x2, int y2))) {
        System.out.println("從 (" + x1 + "," + y1 + ") 到 (" + x2 + "," + y2 + ")");
    }
}

搭配 Switch

record Circle(double radius) {}
record Rectangle(double width, double height) {}
record Triangle(double base, double height) {}

static double area(Object shape) {
    return switch (shape) {
        case Circle(double r) -> Math.PI * r * r;
        case Rectangle(double w, double h) -> w * h;
        case Triangle(double b, double h) -> 0.5 * b * h;
        default -> 0;
    };
}

搭配 Sealed Classes

sealed interface Shape permits Circle, Rectangle {}

record Circle(double radius) implements Shape {}
record Rectangle(double width, double height) implements Shape {}

static double calculateArea(Shape shape) {
    return switch (shape) {
        case Circle(double r) -> Math.PI * r * r;
        case Rectangle(double w, double h) -> w * h;
        // 不需要 default,編譯器知道所有可能
    };
}

實用範例

JSON 節點處理

sealed interface JsonNode permits JsonNull, JsonBool, JsonNumber, JsonString, JsonArray, JsonObject {}

record JsonNull() implements JsonNode {}
record JsonBool(boolean value) implements JsonNode {}
record JsonNumber(double value) implements JsonNode {}
record JsonString(String value) implements JsonNode {}
record JsonArray(List<JsonNode> elements) implements JsonNode {}
record JsonObject(Map<String, JsonNode> fields) implements JsonNode {}

static String stringify(JsonNode node) {
    return switch (node) {
        case JsonNull() -> "null";
        case JsonBool(boolean b) -> String.valueOf(b);
        case JsonNumber(double n) -> String.valueOf(n);
        case JsonString(String s) -> "\"" + s + "\"";
        case JsonArray(List<JsonNode> arr) ->
            arr.stream().map(Main::stringify).collect(Collectors.joining(", ", "[", "]"));
        case JsonObject(Map<String, JsonNode> obj) ->
            obj.entrySet().stream()
               .map(e -> "\"" + e.getKey() + "\": " + stringify(e.getValue()))
               .collect(Collectors.joining(", ", "{", "}"));
    };
}

表達式計算

sealed interface Expr permits Num, Add, Mul, Neg {}

record Num(int value) implements Expr {}
record Add(Expr left, Expr right) implements Expr {}
record Mul(Expr left, Expr right) implements Expr {}
record Neg(Expr operand) implements Expr {}

static int eval(Expr expr) {
    return switch (expr) {
        case Num(int n) -> n;
        case Add(Expr l, Expr r) -> eval(l) + eval(r);
        case Mul(Expr l, Expr r) -> eval(l) * eval(r);
        case Neg(Expr e) -> -eval(e);
    };
}

// -(2 + 3) * 4 = -20
Expr expr = new Mul(new Neg(new Add(new Num(2), new Num(3))), new Num(4));
System.out.println(eval(expr));  // -20

訊息處理

sealed interface Message permits TextMessage, ImageMessage, FileMessage {}

record TextMessage(String sender, String content) implements Message {}
record ImageMessage(String sender, String url, int width, int height) implements Message {}
record FileMessage(String sender, String filename, long size) implements Message {}

static void handleMessage(Message msg) {
    switch (msg) {
        case TextMessage(String sender, String content) ->
            System.out.println(sender + " 說: " + content);
        case ImageMessage(String sender, String url, int w, int h) ->
            System.out.println(sender + " 分享了圖片 " + w + "x" + h);
        case FileMessage(String sender, String name, long size) ->
            System.out.println(sender + " 分享了檔案 " + name + " (" + size + " bytes)");
    }
}

版本歷史

版本功能
Java 14instanceof Pattern Matching (Preview)
Java 16instanceof Pattern Matching (正式)
Java 17Switch Pattern Matching (Preview)
Java 21Switch Pattern Matching (正式)
Java 21Record Pattern (正式)

重點整理

  • instanceof pattern matching 省去手動型別轉換
  • Switch pattern matching 支援類型模式和 null 處理
  • when 關鍵字可加入額外條件
  • Record pattern 可以解構 record 的組成
  • 搭配 sealed classes 實現窮舉檢查
  • 大幅簡化類型判斷和資料處理的程式碼