導入: なぜMatchExceptionが重要なのか
Java 21で本格導入された「switch式」は、コードを簡潔かつ安全に書くための強力な武器です。しかし、この便利さの裏側で、開発者が直面しがちなエラーが「MatchException」です。これは、プログラムが想定していないケース(型)が実行時に現れたとき、Javaが「処理の網羅性が足りていない」と判断して投げる例外です。なぜこの例外が重要かというと、予期せぬ実行時エラーでシステムを落とさないために、コンパイラと協力して「すべてのケースを考慮する」という堅牢な設計を強制できるからです。
基礎知識: 網羅性(Exhaustiveness)とは?
網羅性とは、switch式において「入力される可能性のあるすべてのパターンを処理している状態」を指します。
従来のswitch文では、`default`を書かないことが許されていましたが、switch式(値を返す形式)では、すべてのケースをカバーすることが義務付けられています。特に、`sealed class`(継承を制限したクラス)と組み合わせた場合、Javaは「このクラスのサブクラスはこれだけだから、これら全てを処理すれば網羅されている」と判断します。MatchExceptionは、この「網羅されているはず」という前提が、予期せぬ変更や古いバイナリの読み込みなどによって崩れたときに発生します。
実装/解決策: 安全なswitch式の書き方
MatchExceptionを避けるための基本は、可能な限り`sealed class`を使い、パターンマッチングを利用することです。また、網羅性をコンパイラに保証させることで、実行時の例外を未然に防ぐことができます。もし、将来的にサブクラスが増える可能性がある場合は、例外を投げるのではなく、適切なフォールバック処理を実装する設計が求められます。
サンプルプログラム: MatchExceptionの挙動と回避策
以下のコードは、sealed classを用いたパターンマッチングの例です。
// 継承を制限したクラス(sealed class)
sealed interface Shape permits Circle, Rectangle {}
record Circle(double radius) implements Shape {}
record Rectangle(double width, double height) implements Shape {}
public class Main {
public static void main(String[] args) {
Shape shape = new Circle(5.0);
// switch式で網羅的に処理を行う
// すべてのケース(Circle, Rectangle)が記述されているため安全
double area = switch (shape) {
case Circle c -> Math.PI c.radius() c.radius();
case Rectangle r -> r.width() r.height();
// ここで網羅性が満たされていないとコンパイルエラーになる
};
System.out.println("面積: " + area);
}
}
応用・注意点: 現場で役立つ補足情報
現場で陥りやすいのは、ライブラリの更新や依存関係の変更です。例えば、別ライブラリで定義された`sealed class`に、新しいサブクラスが追加されたとします。あなたのコードを再コンパイルせずに古いクラスファイルだけを実行すると、新しいサブクラスが来た瞬間に「網羅性違反」としてMatchExceptionが発生します。
これを回避するためのヒント:
1. コンパイルの徹底: 依存関係が変更されたら、必ず全体を再コンパイルするCI/CD環境を整えてください。
2. デフォルトの検討: 制御が複雑な場合や、外部の未知のデータを受け取る可能性がある場合は、`default`句や網羅的なパターンを補完するロジックを検討してください。
3. ログの活用: 万が一MatchExceptionが発生した場合に備え、例外発生時の入力値をログに出力するようなグローバルな例外ハンドラを準備しておくことが、シニアエンジニアとしての備えとなります。

コメント