【Java学習|初心者向け】JavaのSealed Classesを使いこなそう!「同一モジュール内での継承制約」の重要性と活用術

1. 導入:なぜ継承を制限する必要があるのか?

Javaでクラスを設計する際、「このクラスは特定のクラスだけを継承させたい」と考えたことはありませんか?通常、publicなクラスは誰でも継承できてしまいますが、これでは設計意図が崩れ、予期せぬバグを招くことがあります。そこで登場したのがSealed Classes(封印クラス)です。これは継承を許可するクラスを明示的に制限する機能で、複雑な条件分岐の管理や、ドメインモデルの安全性を高めるために非常に重要です。

2. 基礎知識:Sealed Classesとは?

Sealed ClassesはJava 17で正式導入された機能です。キーワードとして sealedpermits を使用します。
sealedクラス:継承できるサブクラスをあらかじめ限定します。
permits:継承を許可するクラスを列挙します。
また、これと相性が良いのが「switch式」です。sealedクラスを使うと、コンパイラが「全てのサブクラスを網羅しているか」をチェックしてくれるため、if-elseの複雑な判定から解放されます。

3. 実装:継承を制御する手順

継承を制限するには、親クラスに sealed を付け、permits で許可するクラスを指定します。許可されたサブクラス側では、final(これ以上継承させない)、sealed(さらに継承を制限する)、non-sealed(誰でも継承できるようにする)のいずれかを指定する必要があります。

4. サンプルプログラム:型安全な状態管理

以下のコードは、注文の状態を管理する例です。特定の状態しか存在しないことをコンパイラに保証させます。


// 注文状態を管理する封印クラス
public sealed interface OrderStatus permits Pending, Completed, Cancelled {}

// 各状態をレコードとして定義(finalとして扱われます)
record Pending(String orderId) implements OrderStatus {}
record Completed(String orderId) implements OrderStatus {}
record Cancelled(String orderId) implements OrderStatus {}

public class OrderProcessor {
public String getStatusMessage(OrderStatus status) {
// switch式とyieldを使用して、全ての型を網羅的に処理
return switch (status) {
case Pending p -> {
yield "注文ID: " + p.orderId() + " は現在処理待ちです。";
}
case Completed c -> {
yield "注文ID: " + c.orderId() + " は完了しました。";
}
case Cancelled c -> {
yield "注文ID: " + c.orderId() + " はキャンセルされました。";
}
// sealedのおかげで、default句が不要になります!
};
}
}

5. 応用・注意点

現場で活用する際のポイントは以下の通りです。
網羅性チェックの恩恵:もし将来「Refunded(返金済み)」という状態を追加したくなった場合、OrderStatusのpermitsに追加するだけで、switch式を使っている全箇所でコンパイルエラーが発生します。これにより、変更のし忘れを確実に防げます。
同一モジュール内での制約:sealedクラスのサブクラスは、原則として親クラスと同一パッケージ、あるいは同一モジュール内に配置する必要があります。外部ライブラリから勝手に継承されることを防げるため、APIの堅牢性が飛躍的に向上します。
注意点:無理に全てのクラスをsealedにする必要はありません。状態の遷移や特定のデータ構造など、「型が限定的であるべき場所」に絞って使用するのが、読みやすいコードを維持するコツです。

コメント

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