1. 導入:なぜ継承を制限する必要があるのか?
Java開発において、クラスの継承は強力ですが、無制限に継承を許すと「誰がどこで継承しているか分からない」という保守性の低下を招きます。Java 17から導入された「sealedクラス」と「non-sealedクラス」を組み合わせることで、開発者は「誰に継承を許可し、誰に拒否するか」という設計意図をコードレベルで明示できるようになりました。これにより、安全で堅牢なデータ構造を作ることが可能になります。
2. 基礎知識:sealedとnon-sealedの役割
sealedクラスは、継承できるサブクラスをあらかじめ限定する機能です。
・sealed: 許可されたクラスのみが継承可能。
・permits: 継承を許可するクラスを列挙するキーワード。
・non-sealed: sealedクラスによって制限されていた継承を「再び誰でもできるようにする」ためのキーワードです。
これらを組み合わせることで、「基底クラスは厳格に管理したいが、特定の末端クラスだけは拡張を自由にしたい」といった柔軟な設計が可能になります。
3. 実装:継承の階層を作る
まず基底クラスをsealedにし、特定のサブクラスに許可を与えます。その中で、あえて拡張をオープンにしたいクラスにnon-sealedを指定します。
4. サンプルプログラム
以下のコードをコピーして、Java 17以上の環境で実行してみてください。
// Shape(図形)クラスを定義。継承できるのはCircleとSquareのみと制限
public sealed class Shape permits Circle, Square {
}
// Circleはfinalを付けて、これ以上の継承を禁止(堅牢な設計)
public final class Circle extends Shape {
public void draw() { System.out.println("円を描画します"); }
}
// Squareはnon-sealedを指定し、誰でも継承できるように開放
public non-sealed class Square extends Shape {
public void draw() { System.out.println("四角を描画します"); }
}
// non-sealedなので、さらに別のクラスから継承が可能
class SpecialSquare extends Square {
public void draw() { System.out.println("特別な四角を描画します"); }
}
public class Main {
public static void main(String[] args) {
// switch式と組み合わせることで、網羅性をコンパイラがチェックしてくれる
Shape shape = new Square();
String type = switch (shape) {
case Circle c -> "円です";
case Square s -> "四角です";
// sealedクラスと組み合わせれば、defaultが不要になることも!
};
System.out.println(type);
}
}
5. 応用・注意点
現場で活用する際のポイントは、網羅的チェックです。sealedクラスを使うと、switch式などで「許可されたサブクラス以外が存在しないこと」をコンパイラが保証してくれます。これにより、将来的にサブクラスを追加した際、switch文で処理漏れがあればコンパイルエラーとして検知できるため、バグを未然に防げます。
注意点として、non-sealedクラスを多用すると、せっかくの継承制限が骨抜きになってしまいます。「本当に拡張が必要なクラスなのか?」を慎重に判断し、基本的にはfinalやsealedで閉じ、必要な箇所だけnon-sealedにするという設計を心がけましょう。

コメント