你不知道的Java 新技术:封闭类

封闭类提议进入 JDK 15

2020 年 5 月 13 日,封闭类提案提交审议,目前还没有反对的声音。不出意外的话,该提案会在两个星期内获得批准,并且成为 JDK 15 的一部分。过一段时间,大概三五个星期的样子,你如果想要先睹为快,可以去下载JDK 15的抢先体验版

由于封闭类已经提交审议,并且目前还没有反对的声音和修改的建议,该提案基本定型,我们可以先睹为快了。

我已经有点等不及,快一起来看看这个新技术。

什么是封闭类?

简单地说,封闭类和封闭接口限制可以扩展或实现它们的其他类或接口。封闭类和封闭接口使用类修饰符 sealed

到 Java SE 14 为止,java.lang.constant.ConstantDesc 的声明还是一个常规的公开接口。它的接口声明看起来像下面的样子:

package java.lang.constant;
public interface ConstantDesc {    ...}

复制代码

这个接口或许会在 JDK 15 变更为封闭类,到时候,它的接口声明看起来是下面的样子:

package java.lang.constant;
public sealed interface ConstantDesc    permits String, Integer, Float, Long, Double,            ClassDesc, MethodTypeDesc, DynamicConstantDesc {    ...}

复制代码

这个封闭接口的声明表明,只有 permits 关键字后面的类,包括 String, Integer 等,能够实现这个接口。permits 关键字限定了封闭类或者封闭接口的可扩展范围。

类修饰符 sealed permits 关键字一起,使得封闭类和封闭接口只能在一个限定的、封闭的范围内,获得扩展性。

为什么需要封闭类?

可扩展性不是面向对象编程的一个重要指标吗?为什么要限制可扩展性呢?其实,面向对象编程的最佳实践之一,就是要把可扩展性的限制在可以预测和控制的范围内,而不是无限的可扩展性。

在极客时间专栏《代码精进之路》里,我们讨论了继承的安全缺陷。其中,主要有两点值得我们格外小心(原文摘录,如涉及版权问题,希望极客时间可以许可我这里少许的引用):

  • 一个可扩展的类,子类和父类可能会相互影响,从而导致不可预知的行为。

  • 涉及敏感信息的类,增加可扩展性不一定是个优先选项,要尽量避免父类或者子类的影响。

我们使用了 Java 语言来讨论继承的问题,其实这是一个面向对象机制的普遍的问题,甚至它也不单单是面向对象语言的问题,比如使用 C 语言的设计和实现,也存在类似的问题。

由于继承的安全问题,我们在设计 API 时,有两个要反省思考的问题

  • 一个类,有没有真实的可扩展需求,能不能使用 final 修饰符?

  • 一个方法,子类有没有重写的必要性,能不能使用 final 修饰符?

限制住不可预测的可扩展性,是实现安全代码的一个重要目标。

目前而言,限制住可扩展性只有两个方法,使用私有类或者 final 修饰符。显而易见,私有类不是公开接口,只能内部使用。而 final 修饰符彻底放弃了可扩展性。要么全开放,要么全封闭,可扩展性只能在可能性的两个极端游走。全封闭彻底没有了可扩展性,全开放又面临固有的安全缺陷,这种二选一的状况有时候很让人抓狂,特别是设计公开接口的时候。

通过把可扩展性的限制在可以预测和控制的范围内,封闭类打开了全开放和全封闭两端之间的中间地带,为接口设计和实现提供了新的可能性。

我一点都不会奇怪,这一项技术很快、很快、很快就会被大面积采用。因为,苦可扩展性久矣!你大概可以猜到,我为什么这么着急地要聊聊封闭类了。

怎么使用封闭类?

怎么声明封闭类

封闭类的声明使用 sealed 类修饰符,然后在所有的 extends implements 语句之后使用 permits 指定允许扩展该封闭类的子类。 比如:

public sealed class Shape        permits Circle, Rectangle, Square {    ...}

复制代码

permits 关键字指定的许可子类(permitted subclasses),必须和封闭类处于同一模块(module)或者包空间(package)里。如果允许扩展的子类和封闭类在同一个源代码文件里,封闭类可以不使用 permits 语句,Java 编译器将检索源文件,在编译期为封闭类添加上许可的子类。 比如下面的两种 Shape 封闭类的声明具有一样的运行时效果:

sealed class Shape {   ....}
private final class Circle extends Shape {   ...}
private final class Rectangle extends Shape {   ...}
private final class Square extends Shape {   ....}

复制代码

sealed class Shape permits Circle, Rectangle, Square {   ....}
private final class Circle extends Shape {   ...}
private final class Rectangle extends Shape {   ...}
private final class Square extends Shape {   ....}

复制代码

怎么声明许可类

许可类的声明需要满足下面的三个条件:

  1. 在编译期,封闭类和封闭接口必须可以访问它的许可类;

  2. 许可类必须是封闭类或者封闭接口的直接扩展类或者直接实现类;

  3. 许可类必须声明是否继续保持封闭。

  • 许可类可以声明为终极(final),从而关闭扩展性;

  • 许可类可以声明为封闭类(sealed),从而延续受限制的扩展性;

  • 许可类可以声明为解封类(non-sealed), 从而支持不受限制的扩展性。

比如下面的例子中,许可类 Circle 是终极类,Rectangle 是封闭类,Square 是解封类。

sealed class Shape permits Circle, Rectangle, Square {    ....}
private final class Circle extends Shape {    ...}
private sealed class Rectangle extends Shape    permits TransparentRectangle, FilledRectangle{    ...}
private non-sealed class Square extends Shape {    ....}

复制代码

需要注意的是,由于许可类必须是封闭类的直接扩展,因此许可类不具备传递性。也就是说,上面的例子中,TransparentRectangle Rectangle 的许可类,但不是 Shape 的许可类。

怎么判断许可类?

我们有时候需要判断一个封闭类声明的变量,是由哪一个子类实现的。由于许可类是子类的一个类型,我们的当然可以使用 instanceof 关键字。

Shape flip(Shape shape) {  if (shape instanceof Circle) {      return shape;  } else if (shape instanceof Rectangle) {      return shape.flip();  } else if (shape instanceof Square) {			return shape;	}}

复制代码

不过,由于在编译期,封闭类已经知道了它所有的许可类,许可类的判断可以有更皮实的用法。上面的例子中,如果漏掉某一个 if-else 语句,编译器都不会报错。当然,也不应该报错。这其实也是一种的类的继承机制带来的常见问题。

不受限制的扩展性,使得我们没有办法穷举可能的子类。因此,不论我们怎么努力,我们都没有办法控制可扩展类的安全性和健壮性。封闭类为我们更好地控制子类提供了新的可能性。

通过和型式测试(JEP 375,我们以后有时间聊聊这个新技术)技术的结合,编译器可以帮助我们检查穷举所有的许可类。比如,下面的例子中,如果遗漏掉了某一个 case 语句,编译器就会报错。

Shape flip(Shape shape) {  return switch (shape) {      case Circle c  -> c;		// no action needed      case Rectangle r -> r.flip();      case Square s  -> s;		// no action needed  };}

复制代码

所以,封闭类在把可扩展性的限制在可以预测和控制的范围内同时,还解决了子类穷举的问题。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 《Java核心技术:卷1 基础知识》是一本关于Java编程语言基础知识的电子书。Java是一种通用的、面向对象的编程语言,它被广泛应用于跨平台的应用软件开发。这本书帮助读者建立起Java编程的基础,并深入了解Java的核心技术。 这本电子书的内容主要涵盖了Java语言的基础知识,包括Java的数据型、操作符、控制结构等基本概念。读者可以通过学习这些基础知识来了解Java语言的基本语法和编程规范,并学会如何编写简单的Java程序。 另外,这本电子书还介绍了Java的面向对象编程思想和相关的概念,如、对象、封装、继承和多态等。通过学习这些内容,读者可以理解Java的面向对象编程模型,并学会如何在Java中设计和使用、对象和接口等。 此外,这本书还涉及了Java的输入输出、异常处理、线程和并发编程、集合框架等内容,帮助读者理解和应用Java中常用的编程技术和工具。 总而言之,《Java核心技术:卷1 基础知识》是一本适合初学者学习Java语言的电子书,通过阅读这本书,读者可以建立起Java编程的基础,并且能够进一步学习和应用Java的高级技术和框架。这本书详细介绍了Java的基础知识,并提供了丰富的示例和练习,帮助读者巩固所学的知识。无论是想要学习Java编程的初学者,还是想要进一步提升Java编程技能的程序员,这本电子书都是一个不错的选择。 ### 回答2: 《Java核心技术: 卷1 基础知识》是一本非常重要的Java编程指南,几乎涵盖了Java编程的基础知识。 该书首先介绍了Java语言的核心特性,如数据型、运算符、控制流程等。它详细解释了每个概念,并给出了示例代码,帮助读者更好地理解和应用这些概念。 接下来,该书详细介绍了面向对象编程。它涵盖了和对象的概念,封装、继承和多态的原则,以及如何在Java中利用这些概念来编写可重用和灵活的代码。 该书还深入讨论了Java中最重要的概念之一:异常处理。它介绍了异常处理的原则和机制,并提供了如何编写健壮的代码以处理异常情况的实用技巧。 此外,该书还讨论了Java标准库的使用,包括输入输出、集合框架、多线程和网络编程等方面。它解释了这些库的基本原则和使用方法,并举例说明了如何利用它们来解决实际问题。 总的来说,《Java核心技术: 卷1 基础知识》是一本全面而且详细的Java编程指南。它适用于初学者和有一定经验的开发人员,提供了大量的示例代码和实用技巧,可以帮助读者建立坚实的Java编程基础,并提高他们的编码能力和效率。无论是想从零开始学习Java编程,还是需要深入了解Java核心知识,这本书都是一个非常有价值的资源。 ### 回答3: 《Java核心技术:卷1 基础知识》是一本讲述Java编程语言基础知识的图书,是学习Java语言的重要参考书籍。该书主要涵盖了Java语言的基本概念、语法、面向对象编程等内容,是学习Java语言入门的良好选择。 该书的电子版为电子书的形式,可以通过互联网下载和阅读。电子版可以在计算机、平板电脑、手机等设备上进行阅读,非常方便实用。 《Java核心技术:卷1 基础知识》电子版的优点包括便携性、灵活性和交互性。由于电子版可以随时随地进行阅读,学习者可以在自己方便的时间和地点进行学习。同时,电子版还可以进行搜索、书签标记等操作,方便学习者进行查找和复习。 该书的内容涉及Java语言的基本语法、控制流、数组、字符串、面向对象编程、异常处理、输入输出流等重要概念和技术。这些内容对于Java初学者来说都是非常重要的基础知识,通过学习这些内容可以帮助学习者掌握Java编程语言的基本特性和技术,并为后续学习Java开发框架、高级技术奠定基础。 综上所述,《Java核心技术:卷1 基础知识》电子版是一本非常好的Java学习资料,可以帮助学习者掌握Java编程语言的基本知识和技术,对于初学者来说是非常实用的参考书籍。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值