Javaの新しい機能の1つとして注目されている「シールクラス(Sealed Classes)」は、クラスの継承を制限することでコードの安全性や明確性を高める仕組みです。「このクラスはこのクラスとこのクラスだけが継承できる」という制限があるクラス!というイメージ。
このページでは、Java初心者にも分かりやすいよう、シールクラスの基礎から応用までを1から順を追ってわかりやすく解説します。

シールクラスの特徴や利用方法を理解することで、より安全で保守しやすいJavaプログラムの実装が可能になります。
Java:シールクラス(Sealed Classes)とは
シールクラスは、その名の通りクラスの継承を「封印」し、特定のクラスにのみ継承を許可できる機能です。
従来のJavaでは継承に制限がなく、意図しないサブクラスが派生してしまう可能性がありました。特に、大規模なプロジェクトでは、想定外のクラスが継承関係に入り込むと不具合の原因となり、デバッグが困難になることがあります。シールクラスを導入することで、あらかじめ指定したクラスだけが継承できるようになり、コードの保守性と可読性を向上させます。
なお、シールクラスはJava 15でプレビュー版として登場し、Java 17で正式リリースされました。そのため、実務で本格的に利用する場合はJava 17以上が推奨されます。
ポイント シールクラスが導入された背景
- 型安全性と保守性の向上
自由な継承は、柔軟性と引き換えにコードの可読性や保守性を損なう場合があります。シールクラスを使うと「このクラスはこのクラスとこのクラスだけが継承できる」というように、範囲を限定して明示できます。これにより、想定外の拡張を防止できるだけでなく、どのクラスが継承関係にあるかを一目で把握できるようになります。 - パターンマッチングとの相性
Java 17以降のパターンマッチング機能(特にswitch式の拡張)とシールクラスを組み合わせると、網羅性のチェックをコンパイラが行えます。継承クラスが限られているので、追加されたサブクラスがあればコンパイラが警告してくれることもあり、バグの早期発見に役立ちます。 - レコードクラスとの統合
Java 16でレコード(Recordクラス)が導入され、データ専用のクラスを簡潔に表現できるようになりました。レコードにおいてもシールクラスと同様の発想で、データ構造の安全性や可読性を維持するための仕組みが活用されつつあります。
シールクラスの基本構文
シールクラスでは、クラス宣言に「sealed」を付け、続けて「permits」で継承を許可するクラスを列挙します。サブクラスは「final」「sealed」「non-sealed」のいずれかを指定しなければなりません。具体的には次の通りです。
sealed class Animal permits Dog, Cat { } final class Dog extends Animal { } non-sealed class Cat extends Animal { }
sealed
修飾子:Animal
クラスがsealed
で宣言されているため、Animal
を継承できるクラスはpermits
キーワードで指定されたDog
とCat
のみになります。final
修飾子:Dog
はfinal
なので、さらに継承できません。non-sealed
修飾子:Cat
はnon-sealed
なので、さらに他のクラスが継承できます。
シールクラスを使った開発手順
ここからは、シールクラスを実際に使うときの手順を4つのステップに分けて紹介します。
ステップ1:Javaバージョンの確認
シールクラスは Java 17 で正式に導入されました(Java 15・16ではプレビュー機能)。利用するには、まず Java のバージョンをチェックします。以下のコマンドを実行して、openjdk version "17"
などと表示されれば問題ありません。
java -version
ステップ2:シールクラスの宣言
まずは、シールクラスとして宣言するクラスに sealed
を付与し、どのクラスが継承できるかを permits
で指定します。たとえば、抽象的な図形を表す Shape
クラスは以下のように書けます。
public sealed class Shape permits Circle, Square { public abstract double area(); }
このようにすることで、Shape
クラスを継承できるのは Circle
と Square
のみになります。
ステップ3:サブクラスの実装
Shape
を継承するクラスは「final
」「sealed
」「non-sealed
」のいずれかで宣言しなければなりません。ここでは例として、Circle
と Square
を最終クラス(final
)として定義します。
public final class Circle extends Shape { private double radius; public Circle(double r) { this.radius = r; } @Override public double area() { return Math.PI * radius * radius; } } public final class Square extends Shape { private double side; public Square(double side) { this.side = side; } @Override public double area() { return side * side; } }
ステップ4:動作の確認
最後に、メインメソッドなどで上記のクラスを使います。
public class Main { public static void main(String[] args) { Shape circle = new Circle(5.0); Shape square = new Square(4.0); System.out.println("Circle area: " + circle.area()); System.out.println("Square area: " + square.area()); } }
ここで出力される円と正方形の面積を確認すれば、シールクラスが通常の抽象クラス同様に振る舞いつつ、継承制限が機能していることを把握できます。もし他のクラスが Shape
を継承しようとするとコンパイルエラーが発生します。
sealed / non-sealed / final の使い分け
シールクラスを継承するサブクラスは、次のいずれかの修飾子を必ず指定しなければいけません。
- sealed
継承をさらに制限しつつ、サブクラスに委譲したい場合に使います。例えば、「このクラスを継承できるのは、次の2つのクラスだけ」というように再度制限をかけられます。 - non-sealed
シールクラスから継承したクラスが、再び自由に継承を許可したい場合はnon-sealed
を指定します。特定の段階まで継承を制限し、それ以降はフリーにする、といった柔軟な階層設計を可能にします。 - final
これ以上サブクラスを作らせない場合に用います。最終的な実装クラスや、「もうこれ以上の拡張は必要ない」というクラスに適しています。
実用例(支払い方法のモデル化)
シールクラスは、抽象的な概念と具体的な実装を明確に分けたいときに役立ちます。たとえば、支払い方法をモデル化したい場合、以下のように設計が可能です。
public sealed class PaymentMethod permits CreditCard, BankTransfer, Cash { public abstract void pay(double amount); } public final class CreditCard extends PaymentMethod { @Override public void pay(double amount) { System.out.println("クレジットカードで " + amount + " 円を支払いました。"); } } public final class BankTransfer extends PaymentMethod { @Override public void pay(double amount) { System.out.println("銀行振込で " + amount + " 円を支払いました。"); } } public final class Cash extends PaymentMethod { @Override public void pay(double amount) { System.out.println("現金で " + amount + " 円を支払いました。"); } }
シールクラスとパターンマッチング
Java 17 以降では、パターンマッチング(特に拡張された switch 式)が強化されており、シールクラスと組み合わせることでコードの安全性と可読性をさらに高められます。以下のような switch 式を書いた場合、サブクラスがすべて列挙されていないとコンパイラが警告を出すことがあります。
public void processPayment(PaymentMethod method, double amount) { switch (method) { case CreditCard c -> c.pay(amount); case BankTransfer b -> b.pay(amount); case Cash cash -> cash.pay(amount); } }
シールクラスにより継承可能なクラスが限定されているおかげで、「万が一サブクラスが増えたらどうしよう」という不安を減らせます。