1. 導入:なぜsealedクラスが必要なのか
Javaの開発現場では「クラスの継承」が頻繁に使われますが、設計意図に反して誰でも勝手に継承できてしまうことがバグの温床になることがあります。例えば、ある特定の型のみを扱いたい計算処理で、予期せぬクラスが継承されてくると、システム全体が不安定になります。
そこで登場したのがJava 17から正式導入された「sealed(シール)クラス」です。これを使うと「誰が自分の子クラスになれるか」をプログラマが明示的に管理できるようになり、より堅牢で予測可能なコードが書けるようになります。
2. 基礎知識:sealed修飾子とは?
sealed修飾子は、クラスやインターフェースの「継承先を制限する」ための機能です。通常、クラスはpublicであればどこからでも継承可能ですが、sealedを付けると「許可されたクラス(permitsキーワードで指定)」だけが継承できるようになります。
これにより、コンパイラは「継承関係が完全に把握できている」状態になります。この恩恵を最大限に活かせるのが、switch式での網羅性チェックです。すべてのパターンを網羅していることをコンパイラが保証してくれるため、デフォルトケースを省略しても安全な設計が可能になります。
3. 実装/解決策:sealedクラスの定義とswitch式
sealedクラスを定義する際は、permits句を使って許可するクラスを列挙します。許可された子クラスは、必ずfinal(これ以上継承させない)、sealed(さらに継承を制限する)、またはnon-sealed(制限を解除する)のいずれかを指定する必要があります。
4. サンプルプログラム
以下のコードは、図形の種類を制限し、面積を計算する処理です。これをコピーして動作を確認してみてください。
// 図形の継承関係を厳密に制限します
public sealed interface Shape permits Circle, Rectangle {
double calculateArea();
}
// Circleはfinalとして定義(これ以上継承させない)
public final class Circle implements Shape {
private final double radius;
public Circle(double radius) { this.radius = radius; }
@Override
public double calculateArea() { return Math.PI radius radius; }
}
// Rectangleもfinalとして定義
public final class Rectangle implements Shape {
private final double width, height;
public Rectangle(double width, double height) { this.width = width; this.height = height; }
@Override
public double calculateArea() { return width height; }
}
// 利用側のサンプル
public class Main {
public static void main(String[] args) {
Shape shape = new Circle(5.0);
// switch式とyieldを用いた網羅的な処理
// sealedのおかげで、全てのケースをカバーしているかコンパイラがチェックしてくれます
double area = switch (shape) {
case Circle c -> {
System.out.println("円の面積を計算します");
yield c.calculateArea();
}
case Rectangle r -> {
System.out.println("四角形の面積を計算します");
yield r.calculateArea();
}
// defaultが不要!もしShapeに新しいpermitsを追加し忘れるとコンパイルエラーになるため安全です
};
System.out.println("面積: " + area);
}
}
5. 応用・注意点:現場で陥りやすいポイント
1. permitsの場所: permitsで指定するクラスは、同じパッケージ内にある必要があります(同じモジュール内であれば異なるパッケージでも可能ですが、基本は同じパッケージで定義するのが一般的です)。
2. non-sealedの使いどころ: 継承の制限をあえて解除したい場合はnon-sealed修飾子を使います。これは、フレームワークのプラグイン拡張など、将来的にどのようなクラスが作られるか予測できないケースで使われます。
3. メンテナンス性: sealedクラスを使う最大のメリットは「変更時の安全確保」です。将来的に新しいShapeを追加した際、すべてのswitch文でコンパイルエラーが発生するため、実装漏れを即座に検知できます。
これからのJava開発では、単なる継承よりも「sealedによる安全な継承管理」が標準的な設計パターンとなります。ぜひ積極的に使ってみてください!

コメント