【Java学習|実務向け】Java 17以降の必須知識:Sealed Classesと網羅的なswitch式で堅牢なドメインモデルを構築する

導入

大規模なJavaアプリケーションにおいて、「特定のインターフェースの実装クラスを意図した範囲内に制限したい」と考えたことはありませんか。従来、アクセス修飾子やパッケージプライベートでの制御には限界がありました。Java 17で正式導入されたSealed Classes(シールクラス)は、継承階層を明示的に定義することで、コンパイラによる「網羅性チェック」を可能にします。これにより、if-elseの連鎖や不完全なswitch文によるバグを劇的に減らし、保守性の高いコードを実現できます。

基礎知識

Sealed Classesとは、許可されたサブクラス以外からの継承を禁止する機能です。`sealed`修飾子を付けたクラスやインターフェースは、`permits`句で継承可能なクラスを明示します。
これと相性が良いのが、Java 14以降で導入された「switch式」です。Sealedクラスとswitch式を組み合わせることで、コンパイラが「全てのサブクラスが網羅されているか」をチェックしてくれます。もし新しいサブクラスを追加した場合、対応していないswitch式はコンパイルエラーになるため、実装漏れを未然に防ぐことができます。

実装/解決策

実装のステップは以下の3点です。
1. 親クラスに`sealed`を付与し、`permits`でサブクラスを指定する。
2. サブクラスには`final`(継承終了)、`sealed`(さらに継承)、または`non-sealed`(制限解除)のいずれかを付与する。
3. 利用側ではswitch式を使用して、各サブクラスに対する処理を記述する。

サンプルプログラム

以下のコードは、決済処理を題材にした実用的な例です。

// 1. sealedインターフェースの定義
public sealed interface PaymentMethod permits CreditCard, PayPal, BankTransfer {
}

// サブクラスは継承の性質を明示する必要がある
final class CreditCard implements PaymentMethod { public String getCardNumber() { return "1234-5678"; } }
final class PayPal implements PaymentMethod { public String getEmail() { return "user@example.com"; } }
final class BankTransfer implements PaymentMethod { public String getBankName() { return "J-Bank"; } }

public class PaymentProcessor {
    public String process(PaymentMethod method) {
        // 2. switch式による網羅的な処理
        // もしpermitsに新しいクラスを追加し、ここを更新し忘れるとコンパイルエラーになる
        return switch (method) {
            case CreditCard c -> "カード決済: " + c.getCardNumber();
            case PayPal p -> "PayPal決済: " + p.getEmail();
            case BankTransfer b -> "銀行振込: " + b.getBankName();
            // defaultが不要になるのがsealedクラスの最大のメリット
        };
    }
}

応用・注意点

現場で活用する際のポイントは以下の通りです。

default節の回避: switch式でSealedクラスを使う場合、`default`節を記述しないことを推奨します。`default`を書いてしまうと、新しいサブクラスを追加した際に「網羅性チェック」が働かなくなり、実行時に予期せぬ動作を招くリスクがあるからです。

non-sealedの使いどころ: ライブラリ開発などで、ユーザーに対して拡張の余地を残したい場合は`non-sealed`修飾子を使用します。これにより、継承の制限を意図的に解除できます。

設計の意図: Sealedクラスは「ドメインモデルの変更に弱い」という性質を逆手に取り、ビジネスルールの変更を型システムに強制的に反映させるための強力なツールです。むやみに全てのクラスに適用するのではなく、状態遷移や決済手段、権限管理など「型の網羅性が重要な箇所」に限定して導入するのが、シニアエンジニアとしての賢い設計判断と言えるでしょう。

コメント

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