New Java Feature - Sealed
无法穷举
判断一个形状是不是正方形
- 上述判断 - 一个形状的对象是不是一个正方形的实例
- 一个形状的对象即使不是一个正方形的实例,也可能是一个正方形
- 很多形状的特殊形式就是正方形 - 长方形、菱形、梯形、多边形等 - 无法穷举
- 通过 instanceof 并不能正确判断一个形状是否为正方形
问题根源 - 无限制的扩展性
限制扩展性
- OOP 的最佳实践之一,就是把可扩展性限制在可以预测和控制的范围内,而不是无限的扩展性
- 继承的安全缺陷
- 一个可扩展的类,子类和父类可能会相互影响,从而导致不可预知的行为
- 涉及敏感信息的类,增加可扩展性不一定是个优先选项,要尽量避免父类或者子类的影响
- 在设计 API 时,需要反复思考
- 一个类,有没有真实的可扩展需求,能不能使用 final 修饰符
- 一个方法,子类有没有重写的必要性,能不能使用 final 修饰符
- 限制住不可预测的可扩展性,是实现安全代码、健壮代码的一个重要目标
- 在 JDK 17 之前,限制可扩展性只有两个方法 - 使用私有类或者 final 修饰符
- 私有类不是公开接口,只能内部使用,而 final 修饰符则彻底放弃了可扩展性
- 要么全开放,要么全封闭,可扩展性只能在两个极端游走
- 全封闭彻底没有了可扩展性,而全开放又面临固有的安全缺陷
- 在 JDK 17 后,使用 sealed 修饰的类就是封闭类,使用 sealed 修饰的接口就是封闭接口
- 封闭类和封闭接口限制可以扩展或实现他们的其它类或接口
- 通过把可扩展性的限制放在可以预测和控制的范围内,封闭类和封闭接口打开了全封闭和全开放两个极端之间的中间地带
声明封闭类
- 类型分类
- 被扩展的父类 - 封闭类
- 扩展而来的子类 - 许可类
- 封闭类的声明使用 sealed 类修饰符,然后在所有的 extends 和 implements 语句之后,使用 permits 指定允许扩展该封闭类的子类
Sealed class permits clause must contain all subclasses
All sealed class subclasses must either be final, sealed or non-sealed
- 由 permits 关键字指定许可子类(permitted subclasses)必须和封闭类处在同一个模块(module)或者包空间(package)里
- 如果封闭类和许可类是在同一个模块里,那么他们可以处在不同的包空间里
- 如果许可子类和封闭类在同一个源代码文件里,封闭类可以不使用 permits 语句
- Java 编译器将检索源文件,在编译期为封闭类添加上许可子类
声明许可类
许可类的声明需要满足以下三个条件
- 许可类必须和封闭类处于同一个模块(module)或者包空间(package)里,即在编译时,封闭类必须可以访问它的许可类
- 许可类必须是封闭类的直接扩展类
- 许可类必须声明是否继续保持封闭 - All sealed class subclasses must either be final, sealed or non-sealed
Key | Value | Desc |
---|---|---|
final | 终极类 | 关闭扩展性 |
sealed | 封闭类 | 延续受限制的扩展性 |
non-sealed | 解封类 | 支持不受限制的扩展性 |
Sealed class must have subclasses
- 许可类必须是封闭类的直接扩展,因此许可类不具备传递性
- ColoredCircle 是 Circle 的子类,但 Circle 是解封类,不是 Shape 封闭类的直接扩展,因此 ColoredCircle 不是 Shape 的许可类
案例回顾
如何判断一个形状是不是正方形
- 将形状类定义为封闭类
- Sealed class permits clause must contain all subclasses
- 此时,所有形状的子类是可以穷举的
- 然后需找可以用来表示正方形的许可类
- Shape 是一个封闭类,本质上一个扩展性受限的类,因此我们能穷举所有扩展性
优先级
可扩展性的限定方法 - 优先级由高到低
- 使用私有类
- 使用 final 修饰符
- 使用 sealed 修饰符
- 不受限制的扩展性 - 不推荐 - 失控
All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.