Java 密封类sealed class限制继承层次的模式匹配
Java 密封类(Sealed Classes)和接口在 Java 17 中正式确立,旨在限制哪些类或接口可以扩展或实现它们。通过明确控制继承层次结构,开发者可以构建更安全、更易维护的代码,并配合模式匹配实现穷尽性检查。
1. 定义密封父类或接口
密封类通过限制继承范围,将类型层次结构封闭在一个已知的集合中。
编写密封类或接口时,使用 sealed 关键字修饰,并必须通过 permits 子句明确列出所有允许的直接子类。
输入以下代码定义一个名为 Shape 的密封接口:
public sealed interface Shape permits Circle, Rectangle, Square {
// 定义抽象方法
double area();
}
注意:permits 列表中的子类必须与父类在同一个模块中,或者同一个包中(如果父类未在模块中声明)。
2. 配置子类的继承修饰符
所有在 permits 列表中声明的子类,必须明确其后续的继承可能性。子类只能使用以下三种修饰符之一:
final:表示该类是最终形态,不能再被继承。sealed:表示该类仍然是密封的,需要继续指定permits列表。non-sealed:表示该类恢复为开放状态,可以被任何类继承。
定义不同类型的子类如下:
// 最终类:不能再有子类
public final class Circle implements Shape {
private final double radius;
public Circle(double radius) {
this.radius = radius;
}
@Override
public double area() {
return Math.PI * radius * radius;
}
}
// 密封类:限制特定的子类
public sealed class Rectangle implements Shape permits RoundedRect {
private final double width;
private final double height;
public Rectangle(double width, double height) {
this.width = width;
this.height = height;
}
@Override
public double area() {
return width * height;
}
}
// 非密封类:可以被任何类继承
public non-sealed class Square implements Shape {
private final double side;
public Square(double side) {
this.side = side;
}
@Override
public double area() {
return side * side;
}
}
3. 结合模式匹配进行穷尽性检查
密封类最大的优势在于与模式匹配(特别是 switch 表达式或语句)结合使用时,编译器能够识别所有可能的子类型,从而进行穷尽性检查。
构建一个处理形状的方法,使用 switch 表达式:
public String describeShape(Shape shape) {
return switch (shape) {
case Circle c -> "圆形,半径: " + c.radius;
case Rectangle r -> "矩形,宽: " + r.width + ", 高: " + r.height;
case Square s -> "正方形,边长: " + s.side;
// 如果还有未处理的 sealed 子类(例如未列出的 RoundedRect),编译器会报错
};
}
观察代码:由于 Shape 是密封的,且 Square 被声明为 non-sealed(开放继承),传统的 switch 覆盖检查会有所不同。如果 Shape 的所有子类都是 final 或 sealed(即封闭集合),编译器会强制要求处理所有已知类型。
对于 non-sealed 的子类(如 Square),编译器知道未来可能会有未知的子类出现。因此,为了确保代码的健壮性,添加一个默认分支通常是有必要的,或者确保逻辑上能处理 non-sealed 带来的扩展性:
public String describeShape(Shape shape) {
return switch (shape) {
case Circle c -> "圆形";
case Rectangle r -> "矩形";
// Square 是 non-sealed 的,可能存在 Square 的未知子类
case Square s -> "正方形或其子类";
// 如果不覆盖 default,且存在 non-sealed 子类,某些编译器配置或场景下可能会警告,但通常为了安全建议保留
default -> "未知形状";
};
}
4. 密封类的继承层次结构可视化
为了更清晰地理解密封类如何限制继承路径,请参考以下结构图。该图展示了 Shape 作为父类,如何分发权限给不同的子类,以及子类如何进一步控制自己的继承。
5. 实际应用场景与最佳实践
密封类特别适用于那些“状态有限且固定”的业务场景。
应用于支付系统建模:
假设我们需要处理不同类型的支付方式,且这些类型是确定有限的(如信用卡、支付宝、微信支付)。
- 定义密封接口
Payment。 - 声明
CreditCard、Alipay、WeChatPay为final实现类。 - 编写处理逻辑时,使用
switch表达式。
这样做的好处是,当新增一种支付方式(例如 ApplePay)时,开发者必须修改 Payment 接口的 permits 列表。这一改动会强制导致所有处理 Payment 的 switch 表达式(如果没有覆盖新类型的 case)编译失败,从而提醒开发者补全逻辑,避免了漏写处理分支导致的运行时错误。
应用于状态机管理:
在订单处理系统中,订单状态通常在“待支付”、“已发货”、“已完成”、“已取消”之间流转。
- 创建密封类或接口
OrderState。 - 限制状态转换逻辑,确保只有特定的状态存在。
- 利用编译器检查,确保状态流转处理逻辑覆盖了所有可能的状态,不存在“幽灵状态”。

暂无评论,快来抢沙发吧!