1. 導入:なぜ継承を制限する必要があるのか
Javaのオブジェクト指向において、継承は強力な機能ですが、無制限にクラスを継承できることは時に設計上のリスクとなります。誰でも勝手にサブクラスを作れてしまうと、意図しない拡張によってプログラムの不整合が起きる可能性があるからです。そこでJava 17から導入されたのが「sealedクラス」と「permits句」です。これを使うことで、「誰が継承して良いか」を開発者が明示的に管理でき、より堅牢で安全なコードを書くことが可能になります。
2. 基礎知識:sealedクラスとpermits句とは
まず、sealedクラスとは「継承を許可するサブクラスを制限する」クラスのことです。通常、Javaのクラスはfinalにしない限り誰でも継承できますが、sealedを付けると制限がかかります。
その制限対象を指定するのがpermits句です。permits句を使うことで、「このクラスを継承できるのはAとBだけ」とコンパイル時に厳密に定義できます。これにより、コンパイラは「これ以外のサブクラスは存在しない」ことを保証してくれるため、後述するswitch式と組み合わせた際に、非常に強力なチェックが働きます。
3. 実装と解決策
sealedクラスを定義するには、クラス宣言にsealedキーワードを付け、続けてpermitsキーワードでサブクラスを列挙します。サブクラス側でも「自分は許可された一員である」ことを明示するために、sealed(さらに制限する)、non-sealed(制限を解除する)、またはfinal(継承終了)のいずれかを指定する必要があります。
4. サンプルプログラム
以下のコードは、図形の面積を計算する処理をsealedクラスで実装した例です。
// 継承を許可するクラスを制限する
public sealed class Shape permits Circle, Rectangle {
public abstract double area();
}
// Circleはfinalとして定義(これ以上継承させない)
public final class Circle extends Shape {
private final double radius;
public Circle(double radius) { this.radius = radius; }
public double area() { return Math.PI radius radius; }
}
// Rectangleもfinalとして定義
public final class Rectangle extends Shape {
private final double width, height;
public Rectangle(double width, double height) { this.width = width; this.height = height; }
public double area() { return width height; }
}
// 活用例:switch式で安全に処理する
public class Main {
public static double getArea(Shape shape) {
// コンパイラがShapeのサブクラスがCircleとRectangleのみと知っているため、
// default句を省略しても「網羅されている」と判断されます
return switch (shape) {
case Circle c -> c.area();
case Rectangle r -> r.area();
};
}
}
5. 応用・注意点:現場での活用とバグ回避
現場で活用する際の重要なポイントは、網羅性チェック(Exhaustiveness Check)です。上記のswitch式のように、sealedクラスを使うと、if-elseの連鎖や、漏れがあるswitch文を排除できます。もし将来的に新しいサブクラスを追加したくなった場合、permits句を変更して再コンパイルすれば、そのサブクラスを処理していないswitch文がコンパイルエラーとして教えてくれるため、修正漏れを劇的に減らすことができます。
注意点として、permits句で指定したサブクラスは、同一パッケージ内(または同一モジュール内)に配置する必要があります。外部ライブラリのユーザーに勝手に継承させたくないドメインモデルなどを定義する際に、ぜひこの機能を活用してみてください。コードの意図が明確になり、保守性が飛躍的に向上します。

コメント