Java sealed class

      在〈Java sealed class〉中尚無留言

Seald class 於 Java 17正式釋出。Sealed class 宣告類別 (class) 及介面 (interface) 可被哪些類別繼承或實作,未被 sealed class 允許的類別或介面則不能繼承或實作。目的是避免被任意繼承造成的混亂。

在 class/interface 前使用關鍵字 sealed 定義 sealed class,並在類別名稱後使用 permits 定義允許繼承/實作的類別/介面。

基本語法

// 封閉類別,只允許指定的類別繼承
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 permits Success, Failure {
}

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

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

 

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *