4
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

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

Last updated at Posted at 2023-10-16

初めに

Java21がリリースされました。Java21のメイン機能の1つである「Pattern Matching for switch」について調べていたところ、recordやSealedクラスとどうやら相性が良いらしい。
Java11で知識が止まっている私はrecordもSealedも聞いたことなかったので、この機会にまとめて調べてみました。

recordクラスとは

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

例えば、以下のようなrecordクラスがあるとします。

public record Item(String name, int price) { }

このrecordクラスは、以下のクラスと同様の意味になります。

public final class Item {
    // 特徴1:変数はprivate finalでイミュータブルになる
    private final String name;
    private final int price;

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

    public String name() {
        return name;
    }

    public int price() {
        return price;
    }

    // 特徴3: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 + ']';
    }
}

これが1行で済むのは嬉しいですね。

Sealedクラスとは

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

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を使ってこんな風に書いていたと思います。

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で表現できるようになりました。

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を組み合わせることによるメリットは、全てのルートを網羅した型判定を書くことができるという点です。
例えば以下のような感じです。

// 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の3つに制限しています。
そのため、switch文の中でその3つを記載すれば、全パターンが網羅できていることが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());
}

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

Switchでnullのルートを書けるようになった

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

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新機能まとめ
Javaのレコードクラス
Java 17 の Sealed Classes の書き方

4
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?