Javaにおけるswitch式化とパターンマッチングの進化

# はじめに

パターンマッチングは、値が特定の構造を持つかどうかをチェックし、成功した場合にその構成要素を抽出する仕組みです。
主に関数型言語において実装されていましたが、近年では Ruby, Python, Java などのオブジェクト指向言語にも取り入れられています。

## 断り書き

個人的にパターンマッチングに興味があり、最近 Scheme と Java を初めたので Java のパターンマッチングについてまとめた記事です。

Java にまだ慣れていないのでサンプルコードは Java の記事にも関わらず Scheme(Gauche)、 Python を使っていますが、追々 Java に書き換えていくつもりです。

## Java の switch 文・式

Java は C 言語風の switch 文を最初から持っていたが、面白いことに switch 式が追加された。

- Java SE 12（プレビュー）：JEP 325 で switch 式を初導入  
- Java SE 14（標準機能化）：JEP 361 で正式サポート  
- Java SE 17（プレビュー）：JEP 406 で switch のパターンマッチングを初導入  
- Java SE 21（標準機能化）：JEP 441 で正式サポート  

これによって Java は

- switch 式の便利さ
- 構成要素を抽出する分解

を得た。

## switch 式

下記は 1～12 または "春", "夏", "秋", "冬"を入力すると対応する季節か月のリストを返す処理を switch 式で書いたもの。

```java
Object input = "夏"; // ← ここを 1～12 または "春", "夏", "秋", "冬" に変えて試してください

Object result = switch (input) {
    case Integer month when month >= 1 && month <= 12 -> switch (month) {
        case 3, 4, 5   -> "春";
        case 6, 7, 8   -> "夏";
        case 9, 10, 11 -> "秋";
        case 12, 1, 2  -> "冬";
        default        -> "不明";
    };
    case String season -> switch (season) {
        case "春" -> java.util.List.of(3, 4, 5);
        case "夏" -> java.util.List.of(6, 7, 8);
        case "秋" -> java.util.List.of(9, 10, 11);
        case "冬" -> java.util.List.of(12, 1, 2);
        default   -> java.util.List.of();
    };
    default -> "不明な入力";
};
System.out.println(result);
//[6, 7, 8]

```

単純な数字だけではなく、型で分岐することができる。

## 式・文

switch 文が式になったと言ったが、その利点を説明するためには文と式の違いを理解する必要がある。

一言で言うと**式は値を返す**
文と式の違いを示すため、他の言語を例に出す。

例えば Python にもパターンマッチ機能があるが、match 文という。

```py
# ネストしている配列を平滑化(ネストを排除)する関数
def flatten(lst:list) -> list:
    def loop(atom, acc):
        match atom:
            # 空リスト
            case []:
                return acc
            # リスト
            case [head, *tail]:
                acc = loop(head, acc)
                return loop(tail, acc)
            # 非リスト
            case _:
                return acc + [atom]
    
    return loop(lst, [])

if __name__ == "__main__":
    print(flatten([1,[2,3],4]))
    # [1, 2, 3, 4]
```

このようにパターンマッチは文でも便利だが、実は関数の引数にシュっと差し込むようなことはできない。

### 式の強力さ

式は関数に渡すことができる。Scheme を例に出す。

```scheme
(display (let loop ((x 10) (acc '())) (if (= x 0) acc (loop (- x 1) (cons x acc)))))
;; (1 2 3 4 5 6 7 8 9 10)#<undef>
```

このように、関数の引数の中でネストさせることが容易にできる。

```scheme
;; 1-10 の数を偶奇に分類する
(display
  (let loop ((x 10)
             (even '())
             (odd '()))
    (match x
      [0
       (list (cons 'even (reverse even))
             (cons 'odd (reverse odd)))]
      [_
       (if (even? x)
           (loop (- x 1) (cons x even) odd)
           (loop (- x 1) even (cons x odd)))]))
)
;; ((even 10 8 6 4 2) (odd 9 7 5 3 1))#<undef>
```

ロジックを組み込み、偶数・奇数に分類するといった多少込み入った処理も簡単に表現できる。

## 分解

パターンマッチによって構成要素を引き回すことができる。

```scheme
;;;; 二分木を走査するプログラム
(define tree1 '(5 (4 () ()) (7 () ())))         

;; パターンマッチで巡回
;; 葉の数字を抜き出す
(define (tree-match tree)
  (flatten (match tree
         [() '()]
         [(A B C)
            (list A (tree-match C) (tree-match B))])
         ))

(display (tree-match tree1))
;; (5 7 4)
```

```scheme
;;;; Gauche 版 flatten
(use util.match)
(define (flatten lst)
  (let loop ((atom? lst)
             (acc '()))
    (match atom?
      [(? null? atom?) acc]
      [(? pair? atom?)
        (loop (car atom?)
          (loop (cdr atom?)
            acc))]
      [_ (cons atom? acc)])))

 (display (flatten '(1 (2 3) (4 (5 6)))))
 ;; => (1 2 3 4 5 6)#<undef>
```

このように、パターンマッチは構造化されたデータを直感的かつ宣言的に処理する強力なツールとなる。


## 終わりに

本記事では、「式と文の違い」「パターンマッチによる構造の分解」「Java における switch の式化とパターンマッチの進化」について概観した。

Java においては、`switch` が式になったことで、関数型言語に近い表現が可能になりつつある。加えて、`instanceof` の改良やガード付きパターンなど、Java の進化はまだ途中でありながら、確実に「表現力」を手にしつつある。

## 後でやること

- 変化が起きているので、キャッチアップしたら switch の構文についての説明を追加
    - 9 月にリリースされる Java SE 25 が LTS
- サンプルコードが Scheme, Python であるので追々修正する
- `instanceof` パターンマッチ

## Link

- [JEP 325: Switch Expressions (Preview)](https://openjdk.org/jeps/325)
- [JEP 361: Switch Expressions](https://openjdk.org/jeps/361)
- [JEP 394: Pattern Matching for instanceof](https://openjdk.org/jeps/394)
- [JEP 406: Pattern Matching for switch (Preview)](https://openjdk.org/jeps/406)
- [JEP 440: Record Patterns](https://openjdk.org/jeps/440)
- [JEP 441: Pattern Matching for switch](https://openjdk.org/jeps/441)
- [JEP 507: Primitive Types in Patterns, instanceof, and switch (Third Preview)](https://openjdk.org/jeps/507)