ZetCode

Java 密封类

上次修改于 2025 年 5 月 28 日

本文介绍了 Java 中的密封类,这是 Java 17 中引入的一项功能,旨在强制执行受控的继承层次结构。 密封类允许开发人员限制哪些子类可以扩展给定的类,从而确保更好的设计执行并防止意外的继承。

密封类 提供了对类扩展的显式控制,仅允许指定的子类型继承它们。 这种方法增强了领域建模,强制执行对类层次结构的约束。

密封类的主要特征

基本语法

密封类定义了一组固定的可以扩展它的子类,如下所示。 这种方法确保只有显式允许的类型才能参与继承层次结构,从而在代码库中提供更大的控制和可预测性。

public sealed class Vehicle permits Car, Truck {
    // class members
}

允许的子类型必须指定以下继承类型之一

密封类提供了以下几个优点

简单密封类示例

具有实现的简单密封类。 此示例演示如何声明密封类及其允许的子类,说明了在实践中强制执行受控继承的方式。

Main.java
sealed abstract class Shape permits Circle, Rectangle {
    abstract double area();
}

final class Circle extends Shape {
    private final double radius;
    
    Circle(double radius) { this.radius = radius; }
    
    @Override double area() { return Math.PI * radius * radius; }
}

final class Rectangle extends Shape {
    private final double width, height;
    
    Rectangle(double width, double height) {
        this.width = width;
        this.height = height;
    }
    
    @Override double area() { return width * height; }
}

void main() {

    Shape circle = new Circle(5.0);
    Shape rectangle = new Rectangle(4.0, 6.0);
    
    System.out.println("Circle area: " + circle.area());
    System.out.println("Rectangle area: " + rectangle.area());
}

在此示例中,我们定义了一个密封的抽象类 Shape,它只允许 CircleRectangle 作为其子类。 Shape 类有一个抽象方法 area,它必须由其子类实现。 CircleRectangle 类被标记为 final,这意味着它们不能进一步扩展。 这种结构确保 Shape 层次结构保持受控和可预测,只允许指定的子类参与继承链。

密封类层次结构

创建密封类的层次结构。 本节展示了如何跨多个级别扩展密封类,从而允许复杂但定义明确的继承结构。

Main.java
sealed class Vehicle permits Car, Truck {
    
    protected String manufacturer;
    
    Vehicle(String manufacturer) {
        this.manufacturer = manufacturer;
    }
}

sealed class Car extends Vehicle permits ElectricCar {
    Car(String manufacturer) {
        super(manufacturer);
    }
}

final class ElectricCar extends Car {
    ElectricCar(String manufacturer) {
        super(manufacturer);
    }
}

final class Truck extends Vehicle {
    Truck(String manufacturer) {
        super(manufacturer);
    }
}

void main() {

    Vehicle tesla = new ElectricCar("Tesla");
    Vehicle ford = new Truck("Ford");
    
    System.out.println(tesla.manufacturer);
    System.out.println(ford.manufacturer);
}

此示例演示了一个密封类 Vehicle,它有两个允许的子类:CarTruckCar 类是密封的,只允许 ElectricCar 作为子类。 这种结构允许一个受控的层次结构,其中只有指定的子类可以扩展 Vehicle 类,从而确保继承保持可预测和可管理。

与密封类进行模式匹配

使用密封类进行模式匹配。 使用密封类进行模式匹配可以简洁而详尽地处理所有可能的子类型,使您的代码更安全且更易于维护。

Main.java
sealed class Expr permits Constant, Add, Subtract {
    abstract int eval();
}

final class Constant extends Expr {
    private final int value;
    
    Constant(int value) { this.value = value; }
    
    @Override int eval() { return value; }
}

final class Add extends Expr {
    private final Expr left, right;
    
    Add(Expr left, Expr right) {
        this.left = left;
        this.right = right;
    }
    
    @Override int eval() { return left.eval() + right.eval(); }
}

void main() {
    Expr expr = new Add(new Constant(5), new Constant(3));
    System.out.println("Result: " + eval(expr));
}

int eval(Expr e) {
    return switch (e) {
        case Constant c -> c.eval();
        case Add a -> eval(a.left) + eval(a.right);
        case Subtract s -> eval(s.left) - eval(s.right);
    };
}

密封类支持穷尽的模式匹配,因为所有子类型都是已知的。 此功能允许编译器验证是否处理了所有情况,从而降低了由于未处理的子类而导致运行时错误的风险。

密封接口

Java 还允许密封接口,从而提供相同的控制来决定哪些类或接口可以实现或扩展它们。 密封接口对于建模需要限制类和接口扩展的层次结构很有用,从而确保一组固定的允许实现者。 这种机制有助于维护类型系统中的严格边界并支持健壮的领域建模。

Main.java
sealed interface Shape permits Circle, Rectangle, Polygon {
    double area();
}

final class Circle implements Shape {

    private final double radius;

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

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

final class Rectangle implements Shape {

    private final double width, height;

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

    public double area() {
        return width * height;
    }
}

non-sealed class Polygon implements Shape {

    private final double area;

    Polygon(double area) {
        this.area = area;
    }

    public double area() {
        return area;
    }
}

void main() {

    Shape c = new Circle(3);
    Shape r = new Rectangle(4, 5);
    Shape p = new Polygon(12);

    System.out.println("Circle area: " + c.area());
    System.out.println("Rectangle area: " + r.area());
    System.out.println("Polygon area: " + p.area());
}

此示例演示了一个密封接口 Shape,它有三个允许的实现者:一个 final 的 Circle,一个 final 的 Rectangle 和一个 non-sealed 的 Polygon,它可以进一步扩展。 密封接口提供与密封类相同的好处,包括穷尽的模式匹配和受控的扩展。 通过密封接口,您可以强制执行架构约束并提高代码的可靠性。

non-sealed 关键字

Java 中的 non-sealed 关键字允许密封类或接口的允许子类选择退出密封,使其可以进一步扩展。 这意味着,虽然父类限制了哪些类可以扩展它,但 non-sealed 子类可以被任何其他类自由扩展,从而取消了对其自身层次结构的限制。 当您希望允许类层次结构的某些分支在未来进行扩展,同时保持其他分支的严格控制时,这种灵活性非常有用。

Main.java
sealed class Animal permits Dog, Cat, WildAnimal {}

final class Dog extends Animal {}

non-sealed class Cat extends Animal {}

class PersianCat extends Cat {}
class SiameseCat extends Cat {}

non-sealed class WildAnimal extends Animal {}

void main() {

    Cat genericCat = new Cat();
    PersianCat persian = new PersianCat();
    SiameseCat siamese = new SiameseCat();
    System.out.println("Cat: " + genericCat.getClass().getSimpleName());
    System.out.println("PersianCat: " + persian.getClass().getSimpleName());
    System.out.println("SiameseCat: " + siamese.getClass().getSimpleName());
}

在此示例中,Animal 是一个密封类,它允许 DogCatWildAnimal 作为子类。 Dog 是 final 的,不能进一步扩展。 Cat 被标记为 non-sealed,因此它可以被任何类扩展,例如 PersianCatSiameseCat。 这演示了 non-sealed 关键字如何为特定的子类重新打开继承层次结构,从而在需要时提供更大的灵活性。

WildAnimal 类也被标记为 non-sealed,允许它自由扩展。 这在层次结构中提供了灵活性,同时仍然保持对基类 Animal 的控制。 通过有选择地使用 non-sealed,您可以在类设计中平衡严格性和可扩展性。

何时使用密封类

适当的用例包括需要对一组固定的实现进行建模、定义具有已知变体的域模型、限制 API 实现或利用模式匹配来穷尽处理所有可能的子类型的情况。 密封类在这些上下文中特别有用,因为它们提供了关于类型层次结构结构的编译时保证。

使用密封类的好处包括更好的领域建模、改进的 API 安全性、对穷尽模式匹配的支持以及更易于维护的代码。 通过限制继承,您可以防止意外的扩展,并使您的代码库更易于理解和维护。

来源

JEP 409:密封类

在本文中,我们探讨了用于受控继承的 Java 密封类。 它们通过受限的层次结构使代码更安全和更易于维护。

作者

我叫 Jan Bodnar,是一位热情的程序员,拥有丰富的编程经验。 自 2007 年以来,我一直在撰写编程文章。 迄今为止,我撰写了 1,400 多篇文章和 8 本电子书。 我拥有超过十年的编程教学经验。

列出所有Java教程