1. 導入:なぜこのルールが重要なのか?
Java 17で正式導入された「switch式」や「パターンマッチング」を使う際、コンパイラから「サブクラスを先に記述してください」という警告やエラーを受けたことはありませんか?これは、Javaがプログラムの安全性を守るために定めた非常に重要なルールです。このルールを守ることで、予期せぬ実行時エラーを防ぎ、読みやすく堅牢なコードを書くことができるようになります。
2. 基礎知識:なぜサブクラスが先なのか
Javaの継承関係において、サブクラスは親クラス(スーパークラス)の性質をすべて持っています。そのため、もし「先に親クラスを判定」してしまうと、サブクラスも親クラスの条件に該当したとみなされ、以降のサブクラス判定コードが「到達不能なコード(Unreachable code)」として無視されてしまいます。
例えば、「動物(親)」と「犬(子)」という関係で、先に「動物か?」を確認してしまうと、犬も動物であるため、永遠に「犬か?」という判定にたどり着けません。Javaのコンパイラは、このバグを未然に防ぐために「具体的なもの(サブクラス)から判定しなさい」と強制しているのです。
3. 実装/解決策:正しい記述順序
switch式やif-elseブロックを書く際は、継承関係の範囲が狭い順(具体的→抽象的)に記述するのが鉄則です。
- 悪い例:親クラス → 子クラス(子クラスの判定が実行されない)
- 良い例:子クラス → 親クラス(具体的なケースを先に処理する)
4. サンプルプログラム
以下のコードは、sealed class(封印されたクラス)を活用したswitch式の例です。これをコピー&ペーストして動作確認してみてください。
public class SwitchExample {
// 形状を表すインターフェース(親)
sealed interface Shape permits Circle, Rectangle {}
// 円(子)
record Circle(double radius) implements Shape {}
// 四角形(子)
record Rectangle(double width, double height) implements Shape {}
public static void main(String[] args) {
Shape shape = new Circle(5.0);
// switch式でのパターンマッチング
String result = switch (shape) {
// サブクラス(具体的)を先に記述する
case Circle c -> "円の半径は " + c.radius();
case Rectangle r -> "四角形の面積は " + (r.width() r.height());
// 親クラスを最後に記述する(封印クラスの場合は不要なこともある)
default -> "不明な形状";
};
System.out.println(result);
}
}
5. 応用・注意点:現場で陥りやすい罠
現場でよくあるミスは、if-else文での条件漏れです。
if文で継承関係を判定する場合、instanceofを使ってチェックしますが、ここでも同様にサブクラスからチェックしてください。
また、最近のJavaでは `sealed class`(permitsキーワードで継承先を制限)を使うと、コンパイラが「どのサブクラスを網羅すべきか」を把握してくれるため、switch式で漏れがあれば教えてくれます。これを利用することで、将来的に新しいサブクラスを追加した際にも、コンパイルエラーによって「修正忘れ」を防ぐことができます。
「とりあえず親クラスを先に書く」という癖がある方は、ぜひ「特殊なケースから先に書く」という意識に変えてみてください。それだけで、コードの品質が一段と向上します。

コメント