【升级到Java 17】密封类

提到密封类,需要首先介绍Java的继承机制。

Java继承机制

Java的继承机制的设计理念是开放的。在满足可见性限制的前提下,类默认都是可以被继承的。

这种开放的继承机制在有些情况下会产生问题。比如,子类用来列举设计中可能出现的情况。考虑下面的继承体系结构:

54fa08d00f5bd3ae0b20392689d41a84.png

形状类下的子类有圆形、长方形和三角形。这表示了系统中所支持的形状类型。在开放继承时,如果使用这个库的代码添加了另外一个形状类的子类,比如六边形,可能产生运行时的错误,因为库的代码中并没有相应的支持。

在引入密封类之前,Java中有两种做法可以对继承进行限制。

  • 第一种做法是把类声明为 final,禁止任何子类。

  • 第二种做法是使用包可见的构造器,允许来自同一个 Java 包的子类。

这两种做法都存在一定的局限性。

密封类

密封类在 Java 15 中以预览功能的形式引入,在 Java 16 再次预览,在 Java 17 中成为正式功能。

密封类由 sealed 修饰符和 permits 子句组成。sealed 修饰符表示当前类是密封的,permits 则显式的列出来全部允许的子类。比如,下面代码中的 Shape 类。Shape 只允许3个子类,CircleRectangle 和 Triangle

public abstract sealed class Shape permits Circle, Rectangle, Triangle {

}

public final class Circle extends Shape {

}

public final class Rectangle extends Shape {

}

public final class Triangle extends Shape {

}

可以在 permits 中出现的子类需要满足两个限制:

  • 如果父类在命名模块中,这些类必须在同一个模块中。

  • 如果父类在未命名模块中,这些类必须在同一个包中。

如果子类比较简单,可以把子类和父类放在同一个 Java 源文件中。此时可以省略 permits 子句,而由编译器来自动推断。如下面的代码所示。

public abstract sealed class Shape {

  final class Circle extends Shape {

  }

  final class Rectangle extends Shape {

  }

  final class Triangle extends Shape {

  }
}

每个被允许的子类都需要使用修饰符来描述如何往下传递密封行为。一共有3种选择:

  • 声明为 final 来禁止继续继承。

  • 声明为 sealed 以同样的方式来限制继承。

  • 声明为 non-sealed 来恢复为开放继承。

这3种修饰符必须选择其一,否则会出现编译错误。

下面的代码展示了相关的用法,用到了3种修饰符。

public abstract sealed class Shape {

  final class Circle extends Shape {

  }

  sealed class Rectangle extends Shape {

    final class Square extends Rectangle {

    }
  }

  non-sealed class FreeFormShape extends Shape {

  }
}

密封接口

接口也可以被声明为 sealed, permits 子句中可以包含子接口和实现类。下面的代码给出了示例。

public sealed interface Shape {

  record Point(double x, double y) {

  }

  record Circle(Point center, double radius) implements Shape {

  }

  record Rectangle(Point topLeft, double width, double height) implements
      Shape {

  }
}

由于记录类型都是声明为 final 的。记录类型很适合作为密封类或接口的子类或实现。

反射API和JVM改动

java.lang.Class 中新增了两个与密封类相关的方法。isSealed() 判断 Class 对象是否表示密封类或接口。getPermittedSubclasses() 返回允许的子类的数组。

在Java类文件中,新的属性 PermittedSubclasses 用来记录允许的子类。当 JVM 定义 Java 类时,如果当前类的父类有该属性,则当前类必须出现在父类的该属性所描述的数组中,否则JVM会抛出 IncompatibleClassChangeError 错误。

属性 PermittedSubclasses 的结构如下所示。

PermittedSubclasses_attribute {
    u2 attribute_name_index;
    u4 attribute_length;
    u2 number_of_classes;
    u2 classes[number_of_classes];
}

关于密封类的内容,就介绍到这里。相应的视频见我的B站。

我的新书《Quarkus云原生微服务开放实战》已经出版,欢迎购买。在京东上搜索书名即可。

983f98ac24658a7ea23d64f93ae43bb3.png

猜你喜欢

转载自blog.csdn.net/cheng_fu/article/details/120582764