【Java学習|豆知識】Java 21以降で必須の知識!Switch式の網羅性チェックとSealed Classesの活用術

導入

Javaの進化に伴い、switch文は従来の「値による分岐」から、より強力な「型による分岐」へと進化しました。特にJava 21で正式導入された「Switch式の網羅性(Type coverage)」は、開発者が型安全なコードを書く上で非常に重要です。なぜなら、この機能を正しく使うことで「未定義の型に対する処理漏れ」というバグをコンパイル時に確実に防ぐことができるからです。本記事では、この強力な機能を使いこなすための勘所を解説します。

基礎知識

Javaにおける「網羅性(Exhaustiveness)」とは、switch式において「考えられるすべての入力パターンを漏れなく処理しているか」をコンパイラがチェックする仕組みです。

これと密接に関わるのがsealed classes(封印クラス)です。sealedクラスは、継承できるクラスを明示的に制限します。コンパイラはこの制限を知っているため、switch式でsealedクラスの全サブタイプを処理しているかを検証できます。もし足りないケースがあれば、コンパイルエラーとして指摘してくれるため、将来的な仕様変更による「分岐漏れ」を劇的に減らすことができます。

実装/解決策

網羅性を確保するためのステップは以下の通りです。
1. 階層構造を持つクラスをsealedで定義する。
2. switch式(switch文ではなく、値を返すswitch式)を使用する。
3. すべてのサブタイプを網羅する(あるいはdefault句を適切に配置する)。

もしsealedクラスのすべてのサブタイプを網羅していれば、default句を省略可能です。逆に、網羅できていない場合はコンパイルエラーになるため、メンテナンス性が向上します。

サンプルプログラム

以下は、図形計算を例にしたサンプルです。

// 1. sealedクラスで継承先を制限
sealed interface Shape permits Circle, Rectangle {}
record Circle(double radius) implements Shape {}
record Rectangle(double width, double height) implements Shape {}

public class SwitchCoverageExample {
    public static double getArea(Shape shape) {
        // 2. switch式を使用。型パターンによるマッチング
        return switch (shape) {
            case Circle c -> Math.PI  c.radius()  c.radius();
            case Rectangle r -> r.width()  r.height();
            // sealedクラスで全サブタイプを網羅しているため、defaultは不要
        };
    }

    public static void main(String[] args) {
        Shape myShape = new Circle(5.0);
        System.out.println("面積: " + getArea(myShape));
    }
}

応用・注意点

現場で活用する際の重要な注意点は以下の3点です。

1. 網羅性の恩恵を受けるには「switch式」を使うこと: 従来のswitch文では網羅性チェックが厳密に働かない場合があります。値を返さない処理であっても、網羅性チェックの恩恵を受けるためにswitch式を利用するスタイルが推奨されます。
2. null対策: switch式で型パターンを使用する場合、明示的に「case null -> …」を記述しないと、nullが渡された際にNullPointerExceptionが発生します。必ずnullへのハンドリングを考慮してください。
3. 将来的な拡張性: 新しいサブタイプをsealedクラスに追加した場合、それを参照しているすべてのswitch式でコンパイルエラーが発生します。これは一見手間に見えますが、「修正すべき箇所を即座に特定できる」という非常に強力な安全装置として機能します。

この仕組みを使いこなせば、大規模なリファクタリングも怖くありません。ぜひ次回の開発から積極的に導入してみてください。

コメント

タイトルとURLをコピーしました