【Java21】switchでパターンマッチングができるようになったので、recordやSealedとのシナジーについて調べてみた

# 初めに
Java21がリリースされました。Java21のメイン機能の１つである「[Pattern Matching for switch](https://openjdk.org/jeps/441)」について調べていたところ、recordやSealedクラスとどうやら相性が良いらしい。
Java11で知識が止まっている私はrecordもSealedも聞いたことなかったので、この機会にまとめて調べてみました。

# recordクラスとは
recordクラスとは、データを保持するクラスを簡単に記述できる機能です。
DtoやEntity作る際、classの代わりにrecordを用いることで、記述量を少なくすることができます。
変数もfinalで作成されてイミュータブルになるため、DDDと相性が良いのもgood。


例えば、以下のようなrecordクラスがあるとします。
```Java
public record Item(String name, int price) { }
```
このrecordクラスは、以下のクラスと同様の意味になります。
```Java
public final class Item {
    // 特徴１：変数はprivate finalでイミュータブルになる
    private final String name;
    private final int price;

    // 特徴２：コンストラクタとゲッターがデフォルトで作成される
    public Item(String name, int price) {
        this.name = name;
        this.price = price;
    }

    public String name() {
        return name;
    }

    public int price() {
        return price;
    }

    // 特徴３：equals,hashCode,toStringがデフォルトで作成される
    @java.lang.Override
    public boolean equals(java.lang.Object obj) {
        if (obj == this) return true;
        if (obj == null || obj.getClass() != this.getClass()) return false;
        var that = (Item) obj;
        return java.util.Objects.equals(this.name, that.name) &&
                this.price == that.price;
    }

    @java.lang.Override
    public int hashCode() {
        return java.util.Objects.hash(name, price);
    }

    @java.lang.Override
    public String toString() {
        return "Item[" +
                "name=" + name + ", " +
                "price=" + price + ']';
    }
}
```
これが１行で済むのは嬉しいですね。




# Sealedクラスとは
Sealedクラスとは、継承できるクラスを制限する機能です。
以下のように、親クラスやインターフェースにsealedキーワードとpermitsキーワードをつけることで簡単に制限することができます。

```Java
public sealed interface Book permits Magazine, Novel {
        String getName();
}

public final class Magazine implements Book {
    public String getName() { return "漫画"; }
}

public final class Novel implements Book {
    public String getName() { return "小説"; }
}

// 許可されてないのでコンパイルエラーになる
public final class BookShelf implements Book {
    public String getName() { return "本棚"; }
}
```


# パターンマッチングとは
パターンマッチングとは、ある値が特定のパターンに当てはまるかどうかを判定するという意味です。
Java21では、Switchの中で型判定ができるようになりました。


## Pattern Matching for switch
Java21で登場した「Pattern Matching for switch」では、switchの中で型判定ができるようになりました。
変数の型を判定する場合、今までは`instanceof`を使ってこんな風に書いていたと思います。

```Java
Object a = 123;

if(a instanceof String s) {
    System.out.println(s);
} else if(a instanceof Integer i) {
    System.out.println(i.toString());
} else {
    System.out.println("Unknown type");
}
```

Java21からはswitchで表現できるようになりました。

```Java
Object a = 123;

switch (a) {
    case String s -> System.out.println(s);
    case Integer i -> System.out.println(i.toString());
    default -> System.out.println("default");
}
```
見た目も綺麗で読みやすいですね！

## recordやSealedとのシナジー
いよいよ本題です。
recordとSealedとswitchを組み合わせることによるメリットは、**全てのルートを網羅した型判定を書くことができる**という点です。
例えば以下のような感じです。

```Java
// Itemインターフェースを作成し、permitsで継承先を制限
sealed interface Item permits Car, Book, Beer { }

// Itemを継承したrecordを作成
record Car(String color, String maker) implements Item { }
record Book(String title, Integer price) implements Item { }
record Beer(Float alcohol, String brand) implements Item { }

Item item = new Car("red", "Toyota");

// permitsで継承先を制限しているため、default句が不要になる
switch (item) {
    case Car car -> System.out.println("Car: " + car.color() + " " + car.maker());
    case Book book -> System.out.println("Book: " + book.title() + " " + book.price());
    case Beer beer -> System.out.println("Beer: " + beer.alcohol() + " " + beer.brand());
}
```
上記のソースはpermitsを使用してItemインターフェースの継承先をCar, Book, Beerの３つに制限しています。
そのため、switch文の中でその3つを記載すれば、全パターンが網羅できていることがdefault句を書く必要がありません（コンパイルも通ります）。

また、継承先がすべて網羅できていない場合はコンパイルが通らないため、クラス追加による修正漏れや考慮漏れを未然に防ぐことができます。

```Java
// これだとコンパイルが通らない
switch (item) {
    case Car car -> System.out.println("Car: " + car.color() + " " + car.maker());
    case Book book -> System.out.println("Book: " + book.title() + " " + book.price());
}

|  エラー:
|  switch文がすべての可能な入力値をカバーしていません
```

## Switchでnullのルートを書けるようになった
Java21では、switchにnullのルートを書くことができるようになりました。
これにより、nullだった場合でもぬるぽで落ちることはありません。
```Java
Item item = null;

switch (item) {
    case Car car -> System.out.println("Car: " + car.color() + " " + car.maker());
    case Book book -> System.out.println("Book: " + book.title() + " " + book.price());
    case Beer beer -> System.out.println("Beer: " + beer.alcohol() + " " + beer.brand());
    case null -> System.out.println("null");
}
```
読みやすい！


# 終わりに
今回はJava21で登場したSwitch文でのパターンマッチングについて学習しました。
Java11を使ってる自分は、Javaがここまで進化しているとは知らず驚きました。
次は仮想スレッドについても調べてみたいです。


# 参考
以下の記事を参考にさせていただきました！
ありがとうございます！

[Java 21新機能まとめ](https://qiita.com/nowokay/items/174f75b9e48cc7a80838)
[Javaのレコードクラス](https://qiita.com/value-urano/items/6b37026fbe5c4ac24565)
[Java 17 の Sealed Classes の書き方](https://qiita.com/hanohrs/items/964e9cbf41961e701484)