Java深入解析篇三十三之密封类与接口(Sealed Classes)详解

Java 密封类与接口(Sealed Classes)详解

全面覆盖 Sealed Classes:从基础语法到代数数据类型建模,从模式匹配穷举到设计模式应用

适用版本:JDK 15(预览)→ JDK 17(正式)→ JDK 21+


一、密封类概述

1.1 什么是密封类

密封类(Sealed Classes)是 JDK 17 正式引入的语言特性,允许类或接口精确控制哪些类可以继承或实现它

java 复制代码
// 传统继承:任何类都可以继承
public abstract class Shape { }
// 任何人都可以写:public class Pentagon extends Shape { }

// 密封继承:只有 permits 中的类可以继承
public sealed class Shape permits Circle, Rectangle, Triangle { }
// 编译错误:public class Pentagon extends Shape { }

1.2 解决的核心问题

问题 传统方式 密封类
类型层次不可控 任何类可继承 只有许可类可继承
模式匹配无法穷举 必须写 default 编译器验证穷举
建模意图不明确 靠文档约定 代码层面表达
ADT 无法表达 模拟实现 原生支持

1.3 版本演进

版本 状态
JDK 15 预览
JDK 16 第二次预览
JDK 17 正式标准化

二、基本语法

2.1 密封类

java 复制代码
// 密封类:只有 Circle、Rectangle、Triangle 可以继承
public sealed class Shape permits Circle, Rectangle, Triangle {
    public abstract double area();
}

2.2 密封接口

java 复制代码
// 密封接口:更常用(因为接口可以多实现)
public sealed interface PaymentResult permits PaymentSuccess, PaymentFailed, PaymentPending {
    String message();
}

2.3 permits 子句规则

java 复制代码
// 方式1:显式列出所有子类
sealed class Shape permits Circle, Rectangle, Triangle {}

// 方式2:省略 permits(子类必须在同一文件中)
// Shape.java 文件中:
sealed class Shape {}
final class Circle extends Shape {}     // OK,同一文件
final class Rectangle extends Shape {}  // OK,同一文件

// 方式3:子类在不同包中(需要在同一模块中)
// 模块 module.geometry 中:
sealed class Shape permits com.other.Circle {} // 需要同一模块

2.4 子类的三种选择

java 复制代码
sealed class Animal permits Dog, Cat, Bird {
    abstract void speak();
}

// 选择1:final --- 终止继承链
final class Dog extends Animal {
    @Override void speak() { System.out.println("Woof!"); }
}

// 选择2:sealed --- 继续密封
sealed class Cat extends Animal permits Lion, Tiger {
    @Override void speak() { System.out.println("Meow!"); }
}

// 选择3:non-sealed --- 开放继承
non-sealed class Bird extends Animal {
    @Override void speak() { System.out.println("Tweet!"); }
}

// Bird 是 non-sealed,任何类都可以继承
class Sparrow extends Bird {} // OK
class Eagle extends Bird {}   // OK

// Cat 是 sealed,只有 Lion 和 Tiger 可以继承
final class Lion extends Cat {
    @Override void speak() { System.out.println("Roar!"); }
}
final class Tiger extends Cat {
    @Override void speak() { System.out.println("Grrr!"); }
}

三、密封类与模式匹配

3.1 switch 穷举检查

java 复制代码
sealed interface Shape permits Circle, Rectangle, Triangle {
    double area();
}

record Circle(double radius) implements Shape {
    @Override public double area() { return Math.PI * radius * radius; }
}

record Rectangle(double width, double height) implements Shape {
    @Override public double area() { return width * height; }
}

record Triangle(double a, double b, double c) implements Shape {
    @Override
    public double area() {
        double s = (a + b + c) / 2;
        return Math.sqrt(s * (s - a) * (s - b) * (s - c));
    }
}

// 编译器穷举检查:不需要 default!
// 如果新增一个 Square implements Shape,编译器会在这里报错
static String describe(Shape shape) {
    return switch (shape) {
        case Circle c    -> "圆形,半径: " + c.radius();
        case Rectangle r -> "矩形,宽: " + r.width() + ",高: " + r.height();
        case Triangle t  -> "三角形,边: " + t.a() + ", " + t.b() + ", " + t.c();
        // 没有 default --- 编译器知道已覆盖所有可能
    };
}

3.2 Record Patterns 解构

java 复制代码
// JDK 21:Record Patterns 与密封类组合
sealed interface Expr permits Num, Add, Mul {}
record Num(double value) implements Expr {}
record Add(Expr left, Expr right) implements Expr {}
record Mul(Expr left, Expr right) implements Expr {}

// 解构 + 穷举
String prettyPrint(Expr expr) {
    return switch (expr) {
        case Num(var v) -> String.valueOf(v);
        case Add(var l, var r) -> "(" + prettyPrint(l) + " + " + prettyPrint(r) + ")";
        case Mul(var l, var r) -> "(" + prettyPrint(l) + " * " + prettyPrint(r) + ")";
    };
}

// (2 + 3) * 4
Expr example = new Mul(new Add(new Num(2), new Num(3)), new Num(4));
System.out.println(prettyPrint(example)); // ((2.0 + 3.0) * 4.0)

四、代数数据类型(ADT)建模

4.1 JSON 值类型

java 复制代码
sealed interface JsonValue
    permits JsonString, JsonNumber, JsonBoolean, JsonNull, JsonArray, JsonObject {}

record JsonString(String value) implements JsonValue {}
record JsonNumber(double value) implements JsonValue {}
record JsonBoolean(boolean value) implements JsonValue {}
record JsonNull() implements JsonValue {}
record JsonArray(List<JsonValue> elements) implements JsonValue {}
record JsonObject(Map<String, JsonValue> properties) implements JsonValue {}

// 类型安全的 JSON 渲染
String render(JsonValue value) {
    return switch (value) {
        case JsonString(var s) -> "\"" + s + "\"";
        case JsonNumber(var n) -> String.valueOf(n);
        case JsonBoolean(var b) -> String.valueOf(b);
        case JsonNull() -> "null";
        case JsonArray(var elems) ->
            "[" + elems.stream().map(this::render).collect(Collectors.joining(", ")) + "]";
        case JsonObject(var props) ->
            "{" + props.entrySet().stream()
                .map(e -> "\"" + e.getKey() + "\": " + render(e.getValue()))
                .collect(Collectors.joining(", ")) + "}";
    };
}

4.2 状态机

java 复制代码
// 订单状态机
sealed interface OrderState permits Created, Paid, Shipped, Delivered, Cancelled {
    String description();
}

record Created() implements OrderState {
    @Override public String description() { return "订单已创建"; }
}
record Paid(String paymentId) implements OrderState {
    @Override public String description() { return "已支付: " + paymentId; }
}
record Shipped(String trackingNo) implements OrderState {
    @Override public String description() { return "已发货: " + trackingNo; }
}
record Delivered(LocalDateTime time) implements OrderState {
    @Override public String description() { return "已送达: " + time; }
}
record Cancelled(String reason) implements OrderState {
    @Override public String description() { return "已取消: " + reason; }
}

// 状态转换验证
OrderState next(OrderState current, OrderAction action) {
    return switch (current) {
        case Created c -> switch (action) {
            case Pay p -> new Paid(p.paymentId());
            case Cancel x -> new Cancelled(x.reason());
            default -> throw new IllegalStateException("非法状态转换");
        };
        case Paid p -> switch (action) {
            case Ship s -> new Shipped(s.trackingNo());
            default -> throw new IllegalStateException("非法状态转换");
        };
        // ... 其他状态
        default -> throw new IllegalStateException("终态不可转换");
    };
}

4.3 结果类型(Either)

java 复制代码
// 函数式结果类型
sealed interface Result<T> permits Success, Failure {}
record Success<T>(T value) implements Result<T> {}
record Failure<T>(Exception error) implements Result<T> {}

// 使用
<T> Result<T> tryExecute(Supplier<T> supplier) {
    try {
        return new Success<>(supplier.get());
    } catch (Exception e) {
        return new Failure<>(e);
    }
}

// 处理结果
<T, R> R handle(Result<T> result, Function<T, R> onSuccess, Function<Exception, R> onFailure) {
    return switch (result) {
        case Success<T>(var value) -> onSuccess.apply(value);
        case Failure<T>(var error) -> onFailure.apply(error);
    };
}

// 实际使用
Result<Integer> result = tryExecute(() -> 42 / 0);
String message = handle(result,
    v -> "成功: " + v,
    e -> "失败: " + e.getMessage()
);

4.4 命令模式

java 复制代码
sealed interface Command permits StartCmd, StopCmd, StatusCmd, RestartCmd {
    String serviceName();
}

record StartCmd(String serviceName) implements Command {}
record StopCmd(String serviceName) implements Command {}
record StatusCmd(String serviceName) implements Command {}
record RestartCmd(String serviceName, boolean force) implements Command {}

// 命令处理器
String execute(Command cmd) {
    return switch (cmd) {
        case StartCmd(var name) -> "启动服务: " + name;
        case StopCmd(var name) -> "停止服务: " + name;
        case StatusCmd(var name) -> "查询状态: " + name;
        case RestartCmd(var name, var force) ->
            "重启服务: " + name + (force ? "(强制)" : "");
    };
}

五、密封类与反射

java 复制代码
// 获取密封类信息
Class<?> clazz = Shape.class;

// 检查是否是密封类
System.out.println(clazz.isSealed()); // true

// 获取允许的子类
Class<?>[] permitted = clazz.getPermittedSubclasses();
for (Class<?> c : permitted) {
    System.out.println(c.getName());
    // Circle, Rectangle, Triangle
}

六、最佳实践

6.1 何时使用密封类

场景 是否适合
有限的类型集合(状态、结果) ✅ 非常适合
代数数据类型建模 ✅ 非常适合
模式匹配穷举 ✅ 非常适合
需要扩展的框架接口 ❌ 不适合
第三方库需要实现的接口 ❌ 不适合
类型数量不确定 ❌ 不适合

6.2 设计原则

java 复制代码
// 原则1:密封类搭配不可变实现(Record)
sealed interface Event permits UserCreated, UserDeleted, UserUpdated {}
record UserCreated(String id, String name) implements Event {}
record UserDeleted(String id) implements Event {}
record UserUpdated(String id, Map<String, Object> changes) implements Event {}

// 原则2:密封接口优于密封类(更灵活)
// 接口可以多实现,类只能单继承

// 原则3:配合 switch 表达式使用
// 避免 if-else 链

// 原则4:在模块边界内使用
// 密封类最适合在模块内部定义类型层次

总结:密封类与 Record、Pattern Matching 共同构成现代 Java 类型系统的三件套。密封类控制"谁可以实现",Record 定义"数据长什么样",模式匹配处理"如何分支"。三者组合使 Java 具备了类似函数式语言的代数数据类型建模能力。

相关推荐
论迹复利4 小时前
FreeRTOS 在 RISC-V 上是如何“点火“的 —— 从 main() 到第一个任务的完整链路
java·开发语言·risc-v
张宇Joaquin4 小时前
鲲鹏统一并行加速库KUPL--众核并行能力介绍
java·开发语言·网络
736c4 小时前
C语言-数组 07
c语言·开发语言
宸津-代码粉碎机5 小时前
AI攻防战升级!基于Spring AI构建Java应用自动免疫安全体系
java·大数据·开发语言·人工智能·python·安全·spring
2601_963749106 小时前
越华环保集团数字化污水治理:端边云采集架构与平台对接实现
java·大数据·架构
xcLeigh6 小时前
Go入门:整数类型的溢出与安全边界
java·安全·golang
LorryJovens6 小时前
【LAAP科研】CEN因果等价网络理论---从双缝干涉到因果等价
开发语言·php
源码宝6 小时前
SpringBoot+Vue2 诊所门诊管理系统,完整前后端源码,可直接部署上线
java·源码·his系统·门诊系统·程序代码·诊所系统·门诊his
yychen_java6 小时前
九:Text-to-SQL 智能数据查询与 Human-in-the-Loop 人机协作
java·人工智能·架构