Skip to content

枚举、record 与 sealed 总览 ​

学习目标 ​

  • 知道 enum、record、sealed 分别解决固定取值、数据承载和受限继承问题。
  • 能根据领域模型选择对应类型,而不是把所有状态都塞进普通 class。
  • 知道 JDK 20 的 record patterns 与模式 switch 仍是预览特性。

核心知识点 ​

专业术语 ​

  • enum:由编译器管理固定实例集合的枚举类型。
  • record:用组件声明数据并自动提供值语义成员的数据载体。
  • sealed:限制直接子类型集合的接口或类。
  • record pattern:把 record 组件直接绑定到模式变量的预览语法。

白话解释与边界 ​

enum 表达“只能从这几个值中选一个”,record 表达“这些字段共同构成一个值”,sealed 表达“继承树只允许这些分支”。它们可以组合,但不是替代关系:enum 不适合承载任意业务对象,record 不负责实体生命周期,sealed 也不等于运行时权限控制。

细节案例按用途拆在以下页面:

常用用法 ​

用 enum 表达固定状态 ​

需要表达订单状态或权限级别等固定集合时使用 enum,并应使用稳定业务字段而不是 ordinal 进行持久化。

java
// 语义:需要表达订单状态或权限级别等固定集合时使用 enum,并应使用稳定业务字段而不是 ordinal 进行持久化。
// 初始状态:status 初始为 OrderStatus.PAID。
enum OrderStatus {
    CREATED, PAID, CANCELLED
}

public class EnumOverviewDemo {
    public static void main(String[] args) {
        OrderStatus status = OrderStatus.PAID;
// 初始状态:status 保存枚举常量 PAID,name() 返回 "PAID",ordinal() 返回 1。
        System.out.println(status.name() + ", " + status.ordinal());
        // 输出:PAID, 1
    }
}

用 record 表达小型值对象 ​

需要校验、规范化或保护可变组件时,跳转到 record 数据载体页面查看完整写法。

java
// 语义:需要校验、规范化或保护可变组件时,跳转到 record 数据载体页面查看完整写法。
record Money(String currency, long cents) {}

public class RecordOverviewDemo {
    public static void main(String[] args) {
        Money money = new Money("CNY", 1999);
// 初始状态:money.currency() 为 "CNY",money.cents() 为 1999。
        System.out.println(money.currency());
// 输出:CNY
    }
}

用 sealed 描述有限结果集合 ​

sealed 让新增结果分支变成显式的类型变更,适合编译器帮助检查有限状态模型。

java
// 语义:sealed 让新增结果分支变成显式的类型变更,适合编译器帮助检查有限状态模型。
// 初始状态:result 初始为 new LoginSuccess("u-1")。
sealed interface LoginResult permits LoginSuccess, LoginFailure {}
record LoginSuccess(String userId) implements LoginResult {}
record LoginFailure(String reason) implements LoginResult {}

public class SealedOverviewDemo {
    public static void main(String[] args) {
        LoginResult result = new LoginSuccess("u-1");
// 关键变化:result 接收表达式 new LoginSuccess("u-1") 的计算结果。
// 初始状态:result 的初始值为 new LoginSuccess("u-1")。
        System.out.println(result instanceof LoginSuccess);
        // 输出:true
    }
}

使用 record pattern 拆出 record 组件(JDK 20 预览) ​

需要在 JDK 20 中直接拆出 record 组件时可使用预览版 record pattern,并为编译与运行同时启用预览特性。

java
// 语义:需要在 JDK 20 中直接拆出 record 组件时可使用预览版 record pattern,并为编译与运行同时启用预览特性。
record User(String name, int age) {}

public class PatternOverviewDemo {
    static String label(Object value) {
        if (value instanceof User(String name, int age)) {
            return name + ":" + age;
        }
        return "unknown";
    }

    public static void main(String[] args) {
        User user = new User("Alice", 20);
// 初始状态:user.name() 为 "Alice",user.age() 为 20。
        System.out.println(label(user));
// 输出:Alice:20
    }
}
powershell
javac --release 20 --enable-preview PatternOverviewDemo.java
java --enable-preview PatternOverviewDemo

不常用但需要知道 ​

用 enum 实现字段和方法 ​

枚举可以有字段、构造器和方法,但实例构造器不能由调用方直接调用;需要外部配置或动态扩展时不要硬编码为 enum。

java
enum Level {
    LOW(1), HIGH(2);

    private final int code;

    Level(int code) {
        this.code = code;
    }

    int code() {
        return code;
    }
}

public class EnumFieldDemo {
    public static void main(String[] args) {
        Level level = Level.HIGH;
// 初始状态:level 为 HIGH,level.code() 返回 2。
        System.out.println(level.code());
        // 输出:2
    }
}

用 sealed 的 non-sealed 分支保留扩展点 ​

non-sealed 会从该分支恢复开放继承;具体层次边界和模块规则见 sealed 受限继承页面。

java
sealed interface Event permits BuiltInEvent, ExtensionEvent {}
record BuiltInEvent() implements Event {}
non-sealed class ExtensionEvent implements Event {}
class VendorEvent extends ExtensionEvent {}

public class SealedExtensionOverviewDemo {
    public static void main(String[] args) {
        VendorEvent event = new VendorEvent();
// 初始状态:event 是 ExtensionEvent 的 VendorEvent 实例,因此兼容 Event。
        System.out.println(event instanceof Event);
// 输出:true
    }
}

简单案例 ​

java
enum Status { NEW, DONE }
record Task(String id, Status status) {}

public class ModernTypesOverviewDemo {
    public static void main(String[] args) {
        Task task = new Task("t-1", Status.DONE);
// 初始状态:task.id() 为 "t-1",task.status() 为 DONE。
        System.out.println(task);
        // 输出:Task[id=t-1, status=DONE]
    }
}

这个最小案例把 enum 的固定状态和 record 的数据载体放在一起;需要受限继承时再组合 sealed。

易混点 ​

  • enum 的 ordinal 不是稳定业务编号;需要持久化时定义明确 code,因为序号会随声明顺序变化。
  • record 的 final 组件只保护引用,不会自动深冻结 List、Map 或可变元素。
  • sealed 限制直接子类型,non-sealed 后代仍可继续扩展。
  • record patterns 与模式 switch 在 JDK 20 需要编译和运行两侧的 preview 开关。

课后小问 ​

  1. 为什么 enum、record、sealed 不能互相替代? 答案:它们分别约束值集合、数据结构和继承范围,解决的问题不同。 解析:例如订单状态适合 enum,订单快照适合 record,有限支付结果适合 sealed;组合使用比强行选择一种更清楚。

  2. 什么时候应该跳转到细粒度页面而不是继续看本页? 答案:需要具体 API、构造器校验、浅不可变或 permits 规则时,应直接打开对应细粒度页面。 解析:本页负责建立选择地图,细节页按每个用法提供可复制 Java 案例,适合作为速查入口。

本节小结 ​

  • enum 表达固定实例集合,record 表达值对象,sealed 表达受限继承。
  • record 和 sealed 常组合表达有限的领域结果,enum 可以承载固定状态。
  • JDK 20 record patterns 属于预览语法,命令必须配套。
  • 复杂用法分别见 record 数据载体和 sealed 受限继承页面。

快速回顾 ​

  • 能说出 enum、record、sealed 的核心职责。
  • 能判断状态、值对象和有限层次分别该用什么类型。
  • 能解释 record 的浅不可变边界。
  • 能写出 JDK 20 preview 的编译与运行命令。