【Java学習|初心者向け】Java 17以降の強力な武器!sealedクラスとnon-sealedで継承関係を制御しよう

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にするという設計を心がけましょう。

コメント

タイトルとURLをコピーしました