Java Sealed Class (Java 17+)

Sealed class(封閉類別)允許限制哪些類別可以繼承或實作它,提供更精確的繼承控制。

基本語法

// 封閉類別,只允許指定的類別繼承
public sealed class Shape permits Circle, Rectangle, Triangle {
    // 類別內容
}

// 必須是 final、sealed 或 non-sealed
public final class Circle extends Shape {
    private double radius;
}

public sealed class Rectangle extends Shape permits Square {
    // Rectangle 也是封閉的,只允許 Square 繼承
}

public final class Square extends Rectangle {
    // final 類別不能被繼承
}

public non-sealed class Triangle extends Shape {
    // non-sealed 開放任何類別繼承
}

permits 關鍵字

// 允許的子類別必須:
// 1. 在同一模組(或同一檔案如果是無模組專案)
// 2. 直接繼承封閉類別
// 3. 使用 final、sealed 或 non-sealed 修飾

public sealed class Animal permits Dog, Cat, Bird {
}

public final class Dog extends Animal {}
public final class Cat extends Animal {}
public sealed class Bird extends Animal permits Sparrow, Eagle {}

public final class Sparrow extends Bird {}
public final class Eagle extends Bird {}

封閉介面

public sealed interface Vehicle permits Car, Bike, Truck {
    void start();
}

public final class Car implements Vehicle {
    @Override
    public void start() { System.out.println("汽車啟動"); }
}

public final class Bike implements Vehicle {
    @Override
    public void start() { System.out.println("機車啟動"); }
}

public non-sealed class Truck implements Vehicle {
    @Override
    public void start() { System.out.println("卡車啟動"); }
}

搭配 Record

public sealed interface Result<T> permits Success, Failure {
}

public record Success<T>(T value) implements Result<T> {}
public record Failure<T>(String error) implements Result<T> {}

// Record 預設是 final,所以符合 sealed 的要求

搭配 Pattern Matching

封閉類別和 pattern matching 搭配使用非常強大:

public sealed interface Shape permits Circle, Rectangle, Triangle {}

public record Circle(double radius) implements Shape {}
public record Rectangle(double width, double height) implements Shape {}
public record Triangle(double base, double height) implements Shape {}

public static double calculateArea(Shape shape) {
    return switch (shape) {
        case Circle c -> Math.PI * c.radius() * c.radius();
        case Rectangle r -> r.width() * r.height();
        case Triangle t -> 0.5 * t.base() * t.height();
        // 不需要 default,因為編譯器知道所有可能的子類型
    };
}

同一檔案內的子類別

如果所有子類別都在同一個原始碼檔案中,可以省略 permits

// Shape.java
public sealed class Shape {
    // permits 自動推斷
}

final class Circle extends Shape {}
final class Rectangle extends Shape {}
final class Triangle extends Shape {}

反射 API

Shape shape = new Circle(5);
Class<?> clazz = Shape.class;

// 檢查是否為封閉類別
boolean isSealed = clazz.isSealed();  // true

// 取得允許的子類別
Class<?>[] permitted = clazz.getPermittedSubclasses();
for (Class<?> c : permitted) {
    System.out.println(c.getName());
}

實用範例

狀態機

public sealed interface OrderState permits Pending, Processing, Shipped, Delivered, Cancelled {
}

public record Pending() implements OrderState {}
public record Processing(String processor) implements OrderState {}
public record Shipped(String trackingNumber) implements OrderState {}
public record Delivered(LocalDateTime deliveredAt) implements OrderState {}
public record Cancelled(String reason) implements OrderState {}

public class Order {
    private OrderState state = new Pending();

    public String getStatusMessage() {
        return switch (state) {
            case Pending p -> "等待處理";
            case Processing p -> "處理中,處理人員: " + p.processor();
            case Shipped s -> "已出貨,追蹤碼: " + s.trackingNumber();
            case Delivered d -> "已送達於: " + d.deliveredAt();
            case Cancelled c -> "已取消,原因: " + c.reason();
        };
    }
}

表達式樹

public sealed interface Expr permits Num, Add, Mul {
}

public record Num(int value) implements Expr {}
public record Add(Expr left, Expr right) implements Expr {}
public record Mul(Expr left, Expr right) implements Expr {}

public static int evaluate(Expr expr) {
    return switch (expr) {
        case Num n -> n.value();
        case Add a -> evaluate(a.left()) + evaluate(a.right());
        case Mul m -> evaluate(m.left()) * evaluate(m.right());
    };
}

// 使用:(2 + 3) * 4
Expr expr = new Mul(new Add(new Num(2), new Num(3)), new Num(4));
System.out.println(evaluate(expr));  // 20

Sealed vs 其他方式

方式用途
final class完全禁止繼承
package-private限制同套件可見
sealed class精確控制允許的子類別

重點整理

  • sealed 限制哪些類別可以繼承
  • 子類別必須使用 finalsealednon-sealed
  • permits 列出允許的子類別
  • 同檔案的子類別可省略 permits
  • 搭配 pattern matching 可實現窮舉檢查
  • 適合用於狀態機、領域模型等場景