Java 17 引入了密封类和接口,这一功能旨在提供一种更具声明性的方式来限制继承层级。与阻止所有子类的 `final` 关键字,或将子类限制在同一包中的包私有访问不同,`sealed` 修饰符允许开发者显式列出允许的子类型。例如,一个 `sealed` 接口可以使用 `permits` 子句精确指定哪些类被允许实现它。这在对值的“种类”进行建模时特别有用——例如图形库中不同的形状——作者希望确保系统中只能使用一组特定且已知的类型。
这种设计的核心技术优势之一是它与 switch 表达式的集成,从而提供穷举模式匹配。当 switch 表达式作用于密封类型时,Java 编译器可以确定所有可能的子类型集合。如果开发者涵盖了每一个允许的子类型,编译器就允许省略 `default` 子句。如果缺少某个子类型,编译器将在构建时生成错误。这提供了比标准 switch 语句更高的安全性,因为在标准 switch 语句中,缺失的案例可能直到运行时才被发现。这一机制反映了 Haskell 或 ML 等函数式编程语言中发现的代数数据类型,在这些语言中,数据的结构是严格定义的。
然而,密封类的实际应用涉及 API 稳定性与独立编译方面的重大权衡。虽然编译器在完整构建过程中会强制执行穷举性,但如果消费者代码在密封层级中添加新子类型时未重新编译,编译器就无法做到这一点。如果库作者在公共 API 中添加一个新的子类型,未更新代码的消费者可能会在运行时遇到 `MatchException`。由于这种风险,密封类型通常在组件的内部实现中最有效,因为整个代码库会一起重新编译。当作为公共 API 暴露时,添加新子类型构成破坏性变更,可能需要重大版本更新,以确保所有消费者都能意识到新情况并相应地更新其模式匹配。
此外,Java 的模式匹配仍在不断演进,支持递归模式和字面量模式。这些功能允许更复杂的匹配逻辑,例如嵌套模式(例如匹配包含另一个 record 的 record)以及 switch 中的字面量值。这些进步旨在将 Java 的表达能力更接近 Kotlin 和 TypeScript 等现代语言,同时保持语言的核心面向对象基础。通过将密封类型与这些高级模式相结合,Java 提供了一个框架来创建健壮、类型安全的逻辑,从而最大限度地降低复杂数据处理中未处理边缘情况的风险。
