Java 的 sealed 类与穷尽式模式匹配

Java 的新 sealed 类和模式匹配特性引发了关于该语言应在多大程度上走向代数数据类型和穷尽式 switch,以及这对长期二进制兼容性意味着什么的讨论。评论者将 Java 缓慢、保守、偏运行时的演进方式与 Kotlin 和 Scala 等功能更丰富的 JVM 语言进行对比,争论 Java 是在“追赶”,还是有意保持更小、更稳定。线程中还提出了具体的设计问题——例如 sealed 层次结构如何与库演进相互作用、缺失的空安全和字符串插值,以及安全性、可读性和未来可扩展性之间的权衡。

Sealed 类、模式匹配与二进制兼容性

  • 讨论聚焦于 Java 的 sealed 类、模式匹配,以及它们如何与不断演进的 API 交互。
  • 例子:JDK 的 PEM Encodings API 最初暴露了一个 sealed 类,其允许的子类在后续 JDK 中可能扩展,这会使依赖穷尽式 switch 的客户端代码面临 MatchException 风险。
  • 后来的 JDK 修订增加了一个非公开的允许子类,因此外部代码无法对所有变体做真正穷尽的匹配,从而避免了这一风险。
  • 有些人认为这是一种优雅、众所周知的技巧(类似其他语言里的“非穷尽”注解);也有人认为,为了绕开问题而需要这类变通,暴露了设计上的缺陷。
  • 争论的焦点之一是:新增子类是否应被视为一种二进制不兼容变更,从而要求客户端重新编译;还是像其他链接错误一样,接受运行时失败。

穷尽性 vs 面向未来

  • 一方认为,没有回退分支的穷尽式 switch 很脆弱;对 sealed 层次结构的每个 switch 都应该有 default/回退分支,以获得向前兼容性。
  • 另一方则反驳说,这会削弱代数数据类型和穷尽性检查的核心好处;他们希望获得编译期确定性,并在 API 变化时出现明确的失败。
  • 提出的折中方案是:为“带回退但仍要求当前穷尽性”设计不同的语法/关键字,以同时获得当前安全性和未来兼容性。

Java 的语言演进策略

  • 许多评论将 Java 缓慢、保守的特性引入方式与更快速演进的语言进行对比。
  • 有人为 Java 辩护,称其是刻意扮演“后发者”,借鉴已被验证的想法(通常来自 ML 系语言),但保持语言相对小而稳定,把创新集中在运行时(GC、JIT、遥测)。
  • 也有人抱怨 Java 的步伐感觉“冰河般缓慢”,经常比 Kotlin、Scala 或 C# 晚好几年才跟进,并指出一些缺失或延迟的特性:字符串插值、可空性类型、更丰富的模式匹配、语法糖。
  • 对于 Java 是在“追赶” Kotlin/C#,还是在执行自己更简约的策略、而这些语言则更积极地实验,双方存在分歧。

与 Kotlin、Scala 及其他语言的比较

  • Kotlin:因空安全、智能类型转换和开发者体验而受到称赞;但也被批评为体量大、充满“踩坑点”,且深度绑定 JVM 生态。
  • Scala:因表现力和高级特性而受赞赏,但也被认为难读、风格分裂,并受制于工具链和编译时间。
  • 有人认为,sealed 类和现代模式匹配本质上就是 Scala/C#/ML 的特性以“Java 语法”到来;也有人强调,Java 有意在能力、可读性和广泛可维护性之间保持平衡。