JEP 397:密封课程(第二次预览)
概括
使用密封类和接口增强 Java 编程语言。密封类和接口限制其他类或接口可以扩展或实现它们。这是JDK 16 中的预览语言功能。
历史
密封类由JEP 360提出,并作为预览功能在JDK 15中提供。
此 JEP 建议重新预览 JDK 16 中的该功能,并进行以下改进:
-
_指定上下文关键字_的概念,取代JLS 中先前的_受限标识符_和_受限关键字_的概念。引入字符序列
sealed
、non-sealed
和permits
作为上下文关键字。 -
sealed
与匿名类和 lambda 表达式一样,在确定类或接口的隐式声明的允许子类时,本地类可能不是密封类的子类sealed
。 -
增强缩小引用转换,以对密封类型层次结构的强制转换执行更严格的检查。
目标
-
允许类或接口的作者控制哪些代码负责实现它。
-
提供比访问修饰符更具声明性的方式来限制超类的使用。
-
通过为模式的详尽分析提供基础,支持模式匹配的未来方向。
非目标
-
提供新形式的访问控制(例如“朋友”)并不是目标。
-
final
以任何方式改变都不是目标。
动机
事实证明,类和接口的继承层次结构的面向对象数据模型在对现代应用程序处理的现实世界数据进行建模方面非常有效。这种表达能力是 Java 语言的一个重要方面。
然而,在某些情况下,这种表达能力可以有效地被驯服。例如,Java 支持_枚举类_来模拟给定类仅具有固定数量实例的情况。在下面的代码中,枚举类列出了一组固定的行星。它们是该类的唯一值,因此您可以彻底切换它们,而无需编写子句default
:
enum Planet { MERCURY, VENUS, EARTH }
Planet p = ...
switch (p) {
case MERCURY: ...
case VENUS: ...
case EARTH: ...
}
使用枚举类对固定的值集进行建模通常很有帮助,但有时我们想要对一组固定的_值_进行建模。我们可以通过使用类层次结构来做到这一点,而不是作为代码继承和重用的机制,而是作为列出各种值的方式。以我们的行星示例为基础,我们可以对天文领域中的各种值进行建模,如下所示:
interface Celestial { ... }
final class Planet implements Celestial { ... }
final class Star implements Celestial { ... }
final class Comet implements Celestial { ... }
然而,这种层次结构并没有反映我们的模型中只有三种天体的重要领域知识。在这些情况下,限制子类或子接口的集合可以简化建模。
考虑另一个例子:在图形库中,类的作者Shape
可能希望只有特定的类可以扩展Shape
,因为库的大部分工作涉及以适当的方式处理每种形状。作者感兴趣的是处理已知子类的代码的清晰度Shape
,而不是编写代码来防御未知子类Shape
。允许任意类扩展Shape
,从而继承其代码以供重用,并不是这种情况下的目标。不幸的是,Java 假定代码重用始终是一个目标:如果Shape
可以扩展,那么它就可以通过任意数量的类进行扩展。放松这个假设是有帮助的,这样作者就可以声明一个不能由任意类扩展的类层次结构。在这样一个封闭的类层次结构中,代码重用仍然是可能的,但超出了则不然。
Java 开发人员熟悉限制子类集的想法,因为它经常出现在 API 设计中。该语言在这方面提供了有限的工具:要么创建一个类final
,因此它有零个子类,要么使该类或其构造函数为包私有,因此它只能在同一包中包含子类。JDK 中出现了包私有超类的示例:
package java.lang;
abstract class AbstractStringBuilder { ... }
public final class StringBuffer extends AbstractStringBuilder { ... }
public final class StringBuilder extends AbstractStringBuilder { ... }
当目标是代码重用时,例如让 的子类AbstractStringBuilder
共享其代码,包私有方法非常有用append
。然而,当目标是建模替代方案时,该方法毫无用处,因为用户代码无法访问关键抽象(超类)以switch
覆盖它。如果不诉诸涉及非public
构造函数的脆弱技巧(这对接口不起作用),则无法轻松指定允许用户访问超类而不允许他们扩展它。在声明及其子类的图形库中Shape
,如果只有一个包可以访问Shape
.
总之,超类应该可以被广泛_访问_(因为它代表了用户的重要抽象),但不能广泛_扩展_(因为它的子类应该仅限于作者已知的子类)。这样的超类应该能够表达它是与一组给定的子类共同开发的,既可以为读者记录意图,也可以允许 Java 编译器执行。同时,超类不应过度约束其子类,例如强迫它们final
或阻止它们定义自己的状态。
描述
_密封_类或接口只能由那些允许这样做的类和接口进行扩展或实现。
sealed
通过将修饰符应用于其声明来密封类。然后,在任何extends
andimplements
子句之后,该permits
子句指定允许扩展密封类的类。例如,以下声明Shape
指定了三个允许的子类:
package com.example.geometry;
public abstract sealed class Shape
permits Circle, Rectangle, Square { ... }