導入
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式でコンパイルエラーが発生します。これは一見手間に見えますが、「修正すべき箇所を即座に特定できる」という非常に強力な安全装置として機能します。
この仕組みを使いこなせば、大規模なリファクタリングも怖くありません。ぜひ次回の開発から積極的に導入してみてください。

コメント