sealed classを理解する

継承を無制限に許すと、想定外の種類が階層に追加されることがあります。sealedは直接の子を限定しつつ、子ごとに「ここで終了」「さらに限定」「制限解除」を選べる仕組みです。Java 17の通常機能として扱い、プレビュー機能には依存しません。

この単元で学ぶこと

  • sealedとfinalの違いを説明できる
  • 許可された直接の子と間接の子を区別できる
  • 修飾子・permits・配置からコンパイル可否を判断できる

本文解説

継承を設計する class Shapeを通常のクラスとして公開すると、アクセス可能なコンストラクターなどの条件を満たす限り別のクラスが継承できます。final class Shapeなら継承は一切できません。sealed class Shape permits Circle, Polygonなら、直接継承する型をCircleとPolygonに限定できます。

三つの方針 sealedなクラスの直接のサブクラスにはfinal、sealed、non-sealedのいずれかが必要です。finalはその枝を終了させ、sealedはその先を改めて限定し、non-sealedはその枝の制限を解除します。アクセス修飾子publicやabstractは、この三つの方針の代わりになりません。

全体をコンパイルする例 次をShape.javaに保存し、Java 17のjavac --release 17 Shape.javaでコンパイルします。 sealed class Shape permits Circle, Polygon {} final class Circle extends Shape {} non-sealed class Polygon extends Shape {} class Triangle extends Polygon {} Shapeの直接の子はCircleとPolygonです。TriangleはPolygonを経由する間接の子なので、Shapeのpermitsへ追加する必要はありません。これらは公開トップレベル型ではないため一つのファイルに置けます。

直接の子を検査する 前の例へclass Square extends Shape {}を追加すると、Squareが許可一覧にないためコンパイルできません。final class Squareへ変更しても許可一覧の違反は残ります。逆にSquareをpermitsに加えただけでも、修飾子なしなら直接の子の方針が不足します。許可と修飾子は別々に検査します。

permitsは継承を作らない sealed class A permits B {}と書くだけでは、Bが自動でAを継承することはありません。B側にもextends Aが必要です。許可された型は実際に直接の子でなければなりません。クラスならextends、sealedインタフェースを実装するクラスならimplementsという関係も確認します。

省略できる場合 sealed class A {}とfinal class B extends A {}が同じコンパイル単位、例えば同じA.javaのトップレベル宣言にある場合、コンパイラーはBを推論できるためpermitsを省略できます。別ファイルにある子を、同じパッケージという理由だけで自動推論するわけではありません。

配置の制約 名前付きモジュールに属するsealed型とその許可された直接の子は同じモジュール内に置きます。名前なしモジュールの場合は同じパッケージ内に置きます。この配置規則とアクセス可能性は別条件です。別モジュールの子をpublicにしても配置違反は解消しません。

インタフェースとの比較 sealed interface Expr permits Num {}に対してrecord Num(int value) implements Expr {}と宣言できます。recordは暗黙にfinalだからです。インタフェースの直接のサブインタフェースはsealedまたはnon-sealedを選びます。インタフェースへfinalを付けることはできません。

例題と検証手順 先のShape例でnon-sealed class Polygonをfinal class Polygonに変更すると、Triangle extends Polygonが違反になります。まず元の例をコンパイルし、その後一箇所だけ変更して再コンパイルすると、エラー原因を分離できます。sealedを付けただけでShapeがabstractになるわけではなく、具象クラスならnew Shape()も可能です。

Java 17の境界 sealed型はJava 17で正式な機能ですが、型パターンを用いるswitchなどにはバージョンやプレビュー指定の違いがあります。この課ではsealedだけから「すべてのswitchが網羅性判定される」と一般化せず、宣言の妥当性と継承関係に集中します。

図解

許可する子と枝の終わり方
TriangleはShapeの間接の子。Polygonのnon-sealedによって通常の継承が可能になる。

重要ポイント

  • permitsは直接の子の一覧であり、継承宣言を代行しない。
  • 子はfinal・sealed・non-sealedでその先の方針を決める。
  • non-sealedの先は通常の継承規則へ戻る。
  • 名前付きモジュールと名前なしモジュールでは配置条件が異なる。

間違えやすい点

  • abstractをfinal・sealed・non-sealedの代わりにしない。
  • 同じパッケージだけを理由にpermitsを省略しない。
  • sealedを暗黙のabstractや暗黙のfinalと考えない。

用語解説

sealed
直接の子となれる型を限定する修飾子。
permits
sealed型が許可する直接の子を列挙する節。
non-sealed
sealedな親から受けた継承制限をその枝で解除する修飾子。
直接の子
間に別の型を挟まず、その型を継承または実装する型。
コンパイル単位
一つのソースファイルに相当する宣言のまとまり。

関連教材

参考資料

教材データに登録された参考リンクです。リンク先の内容や更新日は提供元でご確認ください。

確認問題の出題・採点や学習履歴の保存にはログインが必要です。