【プログラミングしてみよう39】JavaのSealed Classes入門

Java

前回はvarを使った型推論でコードをすっきり書く方法を学びました。

今回は「Sealed Classes(封印されたクラス)」について解説します。 Java 17から正式導入されたSealed Classesを使うことで、「このクラスを継承できるのはこれだけ」と継承関係を明確に制限できるようになります。

Sealed Classesとは

以前学んだ継承では、extends を使えば誰でも自由にクラスを継承できました。しかし場合によっては「このクラスを継承できるのは決まったクラスだけにしたい」という設計が必要になることがあります。

たとえば「図形」を表すクラスがあるとして、「円」「長方形」「三角形」だけを図形として扱いたい場合に、Sealed Classesを使えばその3つ以外が継承できないよう制限できます。

public sealed class クラス名 permits サブクラス1, サブクラス2 { }

permits の後ろに、継承を許可するクラスを列挙します。許可されたサブクラスは以下の3種類のいずれかで宣言する必要があります。

final    // これ以上継承できない(継承の末端)
sealed   // さらに継承を制限して続ける
non-sealed // 継承制限を解除して自由に継承できるようにする

サンプルコード(Sealed Classesの基本)

ファイル作成

「Java27」フォルダに Shape.javaCircle.javaRectangle.javaTriangle.javaSealedTest.java という名前でファイルを作成します

コード入力

Shape.java に以下のコードを入力します。

public abstract sealed class Shape permits Circle, Rectangle, Triangle {
    abstract double area();
}

Circle.java に以下のコードを入力します。

public final class Circle extends Shape {
    private final double radius;

    Circle(double radius) {
        this.radius = radius;
    }

    @Override
    double area() {
        return Math.PI * radius * radius;
    }

    @Override
    public String toString() {
        return "円(半径: " + radius + ")";
    }
}

Rectangle.java に以下のコードを入力します。

public final class Rectangle extends Shape {
    private final double width;
    private final double height;

    Rectangle(double width, double height) {
        this.width = width;
        this.height = height;
    }

    @Override
    double area() {
        return width * height;
    }

    @Override
    public String toString() {
        return "長方形(" + width + " x " + height + ")";
    }
}

Triangle.java に以下のコードを入力します。

public final class Triangle extends Shape {
    private final double base;
    private final double height;

    Triangle(double base, double height) {
        this.base = base;
        this.height = height;
    }

    @Override
    double area() {
        return base * height / 2;
    }

    @Override
    public String toString() {
        return "三角形(底辺: " + base + ", 高さ: " + height + ")";
    }
}

SealedTest.java に以下のコードを入力します。

import java.util.List;

public class SealedTest {
    public static void main(String[] args) {
        var shapes = List.of(
            new Circle(5),
            new Rectangle(4, 6),
            new Triangle(3, 8)
        );

        for (var shape : shapes) {
            System.out.printf("%s の面積: %.2f%n", shape, shape.area());
        }
    }
}

コンパイル

javac Shape.java Circle.java Rectangle.java Triangle.java SealedTest.java

実行

java SealedTest

実行結果は次のようになります。

円(半径: 5.0) の面積: 78.54
長方形(4.0 x 6.0) の面積: 24.00
三角形(底辺: 3.0, 高さ: 8.0) の面積: 12.00

サンプルコード(recordとの組み合わせ)

以前学んだ record とSealed Classesは非常に相性がよく、データの種類を明確に表現するのに便利です。

ファイル作成

「Java27」フォルダに Result.javaResultTest.java という名前でファイルを作成します

コード入力

Result.java に以下のコードを入力します。

public sealed interface Result permits Result.Success, Result.Failure {
    record Success(String message) implements Result { }
    record Failure(String errorMessage) implements Result { }
}

ResultTest.java に以下のコードを入力します。

public class ResultTest {
    public static Result divide(int a, int b) {
        if (b == 0) {
            return new Result.Failure("0で割ることはできません");
        }
        return new Result.Success(a + " / " + b + " = " + (double) a / b);
    }

    public static void main(String[] args) {
        var result1 = divide(10, 3);
        var result2 = divide(10, 0);

        printResult(result1);
        printResult(result2);
    }

    static void printResult(Result result) {
        if (result instanceof Result.Success s) {
            System.out.println("成功: " + s.message());
        } else if (result instanceof Result.Failure f) {
            System.out.println("失敗: " + f.errorMessage());
        }
    }
}

コンパイル

javac Result.java ResultTest.java

実行

java ResultTest

実行結果は次のようになります。

成功: 10 / 3 = 3.3333333333333335
失敗: 0で割ることはできません

コードの解説

sealed class Shape permits Circle, Rectangle, Triangle は、Shape を継承できるのは CircleRectangleTriangle の3つだけと宣言しています。permits に含まれていない別のクラスが extends Shape しようとするとコンパイルエラーになります。

サブクラスに final をつけることで、継承の末端として「これ以上継承できない」という制限をかけています。abstract double area() は親クラスで処理を定義せず、サブクラスに実装を強制するメソッドです。各図形に合った面積の計算式をそれぞれのクラスで実装しています。

Math.PI は円周率(π)を表す定数で、Math クラスに用意されています。Math.pow()Math.sqrt() など、数学的な計算はこの Math クラスにまとまっています。

System.out.printfString.format と同じ書式指定で出力できるメソッドです。%.2f で小数点2桁に丸めて表示しています。

Result の例では、sealed interface とすることでインターフェースにも継承制限をかけています。record と組み合わせることで「成功か失敗かのどちらかしかない」という構造を型で表現できます。result instanceof Result.Success s のように、Java 16から使える「パターンマッチング」を使うと、型チェックと変数への代入を1行でまとめて書けます。

まとめ

Sealed Classesを使うことで、継承できるクラスをあらかじめ限定し、クラス階層を明確に設計できます。sealed で許可するクラスを permits で列挙し、サブクラスには finalsealednon-sealed のいずれかをつける必要があります。record と組み合わせると「成功か失敗か」「円か長方形か三角形か」といった、取りうる種類が限定されたデータ構造をシンプルに表現できます。Java 17以降の機能であるため、使用するJavaのバージョンを確認してから活用しましょう。

2026/07/18(土) ぱんまる

コメント

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