了解 Java 的密封类

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

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

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

形状类

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

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

  • 第一种做法是把类声明为 final,禁止任何子类。
  • 第二种做法是使用包可见的构造器,允许来自同一个 Java 包的子类。

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

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

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

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个修饰符必须选择其一。

下面的代码展示了相关的用法。

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 {

  }
}

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

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 的。记录类型很适合作为密封类或接口的子类或实现。

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];
}
版权所有 © 2024 灵动代码