密封类(Sealed Classes),这个概念在许多语言中都存在。例如,在 C#中的密封类表示表明该类是最终类(不可被继承);在 Scala 中密封类表示 case 类的子类只能限定在当前源文件中定义;在 Kotlin 中密封类要求其子类只能在当前源文件中定义。
尽管不同语言中的密封类的概念各不相同,但总的来说都是对子类的继承进行了限制,只不过这个继承的限制范围和程度各不一样。
那么密封类的密封概念就比较清晰了,即:限制类的继承。
对于面向对象语言来说,对象间的继承、实现关系是为了对类能力的扩展和增强。但是当这种增强的能力无法被原系统支持的时候,则会导致系统出现不可预见的异常逻辑。
所以为了避免开发人员错误地重用一些代码,我们需要对类的继承扩展能力进行限制,以确系统的可控。那种控制需要满足两种条件:
其中第一点是对无序扩展的限制,第二点是在需要进行扩展的时候,仍然可以基于其一种扩展能力,但是风险并不通过提供能力的组件承担,而是用进行声明的用户进行承担。
显然这两种能力中,第一种能力是必须的,第二种能力是扩展的。在第一节中的三种语言中实现的密封类都是实现了第一种控制需求,并没有对第二种进行扩展。
如果是针对于密封类的基本特性来说,Java 在很早以前就已经支持了这种特性,而实现这种能力的方式有如下两种:
其中,通过 final 关键字修饰可以将类描述为“最终态”,此时的 final 类是无法被继承的,这种将类描述为最终态的形式,是和 C#的密封类的特性一致的。
而使用包私有类让该类的子类仅在该包下进行继承,这种限制的形态类似于 Scala 与 Kotlin 的限制条件:允许满足特定情况的继承。
所以,从广义的密封概念上来讲,Java 很早就支持了对于对类密封的概念,但是与上文举例的其他语言一样,其仅仅进行了类的限制,没有对用户开放选择性。
针对于这种扩展的能力需求,Java 引入了密封类的概念。
密封类(Sealed Classes)的首次提出是在 Java15 的 JEP 360 中,并在 Java 16 的 JEP 397 再次预览,而在 Java 17 的 JEP 409 成为正式的功能。从基本概念上来说,三个版本中的密封类并不不同,如果是已经在之前预览版本中对密封类有了解的同学可以大胆使用了!
首先 Java 中密封类(Sealed Classes)的核心是: 通过 sealed 修饰符来描述某个类为密封类,同时使用 permits 关键字来制定可以继承或实现该类的类型有哪些。注意 sealed 可以修饰的是类(class)或者接口(interface),所以 permits 关键字的位置应该在 extends 或者 implements 之后。
以下为描述一个接口为密封类的写法实例:
public sealed interface dogService extends animalService permits dogServiceImpl {
void doSomething();
}
密封类支持三种形式的子类
public sealed class dogServiceImpl implements dogService permits moreDogService {
@Override public void doSomething() { }}
public final class dogServiceImpl implements dogService {
@Override public void doSomething() { }}
public non-sealed class dogServiceImpl implements dogService {
@Override public void doSomething() { }
static class bigDogExtend extends dogServiceImpl { }}
首先,对于密封类来说,其子类如果仍然是密封的类,说明由下游调用方继续提供密封保障。而如果是最终态类的话,则指定类已经形成完全密封,所以满足密封保障。而第三种使用non-sealed
关键字对类进行显式的声明其不进行密封,这种情况下由下游调用方承担打破密封的风险(但是我是觉得竟然 java 都开始使用包含‘-’的关键词了,太丑陋了我无法接受)。
从密封性的角度上来说,sealed 子类传递了密封性;final 的子类确认密封性;non-sealed 显式放弃密封性。
我们可以发现,第三种 non-sealed 类型的关键字便为用户提供了一种可以当用户认同风险的情况下突破限制的手段,所以我们认为在密封灵活性的方面 Sealed Classes 能力在保持密封特性的前提下提供了极大的扩展性。
注意,由于要保证密封,所以类与类之间的密封传递是不能跨越继承层级的,即:
对类的继承,子类必须为 permits 关键字声明类的直接实现类。
这部分各位可以自己试一下。
同样的,由于密封类的实现是为了控制继承关系,所以密封类不支持隐式的继承关系,所以:
密封类不支持匿名类与函数式接口。
总结一下,Java 本身支持粗粒度的类的密封性保障。而在此基础上密封类提供了更细粒度的继承关系控制。密封类的主要新增关键词有sealed
、permits
、non-sealed
。子类继承方式sealed
传递了密封性,final
确认了密封性,non-sealed
显式声明破坏密封性。给予密封性的控制,所以要求子类必须为 permits 后声明的直接子类,所以也不支持匿名类与函数式接口。
作为 Java 17 中转正的功能,这是十分重要的特性之一,值得好好学习一下。
领取专属 10元无门槛券
私享最新 技术干货