Rec-04: Sealed Classes との組み合わせ
sealed interface とrecord を組み合わせると、 「種類が限定された型の集合」を型安全に表現できます。 Java 17 以降で利用可能なこのパターンは、switch パターンマッチングと組み合わせることで追加漏れをコンパイル時に検出できます。
sealed interface + record とは
「代数的データ型(ADT)」とは、「種類が決まった型の集合」を表す考え方です。 たとえば図形は「円・長方形・三角形のいずれか」と決まっています。 sealed interface を使うと、このような「種類が限定された型」をコンパイラに伝えることができます。
sealed interface の利点
- 型の追加制限:
permitsで許可したクラス以外はサブタイプになれない - 網羅性チェック: switch パターンマッチングで全バリアントを処理しないとコンパイルエラーになる
- record との相性: 各バリアントを record で定義すると値の保持も簡潔に書ける
Java バージョンごとの違い
- Java 8: sealed / record が使えないため、abstract クラス + enum で近似実装します
- Java 17: sealed interface と record が使えます。instanceof パターンマッチング(Java 16+)で型安全に処理できます
- Java 21: switch パターンマッチングが正式化。sealed interface との組み合わせで全ケースの網羅性をコンパイラが保証します
サンプルコード
Java 8 では sealed クラスが使えないため、abstract クラス + enum で「種類が限定された型」を表現します。ただし、この方法では新しいサブクラスを誰でも追加できてしまい、switch の default 漏れをコンパイル時に検出できないという弱点があります。
よくあるミス・注意点
⚠️ sealed interface のサブタイプはトップレベルか static ネストでなければならない
sealed interface の permits に指定できるのは、同じパッケージ内のトップレベルクラスか、static なネストクラスです。 メソッドの中で定義したローカルクラスや非 static な内部クラスは permits に指定できずコンパイルエラーになります。 各バリアントの record は必ず static なネストとして定義してください。
⚠️ switch に default を書くと網羅性チェックが無効になる
sealed interface との switch パターンマッチングに default ブランチを追加してしまうと、 新しいバリアントを permits に追加したときに switch の更新漏れがコンパイルエラーにならなくなります。 sealed interface との switch では default は極力書かないようにしましょう。
⚠️ sealed interface は Java 17+ の機能(Java 16 はプレビュー)
sealed interface は Java 17 で正式リリースされました。Java 16 ではプレビュー機能のため--enable-preview フラグが必要です。 Java 8 や Java 11 では使用できません。
テストする観点
- Circle・Rectangle・Triangle それぞれの
area()が正しい値を返すこと(境界値: radius=0 は面積 0) describe()が各バリアントに対して正しい文字列を返すこと- sealed interface の permits に含まれていない型を instanceof チェックしようとするとコンパイルエラーになること
- Java 21 の switch パターンマッチングで全バリアントが処理されること
- 負の半径など不正な値に対するバリデーション(コンパクトコンストラクタでのチェック)が動作すること