本文是对官方文档(https://openjdk.org/jeps/409)的概括总结,更详细的内容请看官方文档的不完全翻译(https://blog.youkuaiyun.com/weixin_38833041/article/details/125398354)
简介
密封的类和接口限制了哪些其他类(子类)或接口可以扩展或实现它们(父类、父接口),更细致的限制了超类的使用。
密封类的语法
类声明语法
密封类或接口只能由允许的类和接口扩展或实现。使用sealed修饰符来声明密封类。然后,在extends和implements之后,使用permits指定允许扩展密封类的类。
NormalClassDeclaration:
{ClassModifier} class TypeIdentifier [TypeParameters]
[Superclass] [Superinterfaces] [PermittedSubclasses] ClassBody
ClassModifier:
(one of)
Annotation public protected private
abstract static sealed final non-sealed strictfp
PermittedSubclasses:
permits ClassTypeList
ClassTypeList:
ClassType {, ClassType}
例如:
package com.example.geometry;
public abstract sealed class Shape
permits Circle, Rectangle, Square { ... }
当permits的子类的大小和数量较小时,可以将它们声明在与密封类相同的源文件中,同时可以省略permits语句,Java 编译器将从源文件中的声明推断出permits的子类。permits 指定的类必须有一个规范的名称,否则会报告编译时错误。这意味着匿名类和局部类不能成为密封类的子类型。
abstract sealed class Root { ...
final class A extends Root { ... }
final class B extends Root { ... }
final class C extends Root { ... }
}
密封类对子类的约束
- 密封类及其允许的子类必须属于同一个模块,并且如果在未命名的模块中声明,则必须属于同一个包。
- 每个允许的子类都必须直接继承密封类。
- 每个允许的子类都必须使用final、sealed或non-sealed修饰符中的一个来描述:
- 允许的子类声明为 final 表示它无法再扩展。
- 允许的子类声明为sealed表示它可以以一种受限制的方式进一步扩展。
- 允许的子类声明为non-sealed表示它可以任意的扩展。
作为第三个约束的示例,Circle 和 Square 是 final 而 Rectangle 是密封的,WeirdShape是非密封的:
package com.example.geometry;
public abstract sealed class Shape
permits Circle, Rectangle, Square, WeirdShape { ... }
public final class Circle extends Shape { ... }
public sealed class Rectangle extends Shape
permits TransparentRectangle, FilledRectangle { ... }
public final class TransparentRectangle extends Rectangle { ... }
public final class FilledRectangle extends Rectangle { ... }
public final class Square extends Shape { ... }
public non-sealed class WeirdShape extends Shape { ... }
密封类的可访问性
允许的子类和它的密封超类必须可以相互访问,因为 extends 和 permit 子句使用类名。但是,permits的子类不需要彼此具有相同的可访问性,也不必与密封类具有相同的可访问性。
密封接口
和密封类相似,可以使用sealed 修饰符来密封接口,使用permits指定允许的实现类和子接口。
例如下面是一个类层次结构的经典示例:建模数学表达式。
package com.example.expression;
public sealed interface Expr
permits ConstantExpr, PlusExpr, TimesExpr, NegExpr { ... }
public final class ConstantExpr implements Expr { ... }
public final class PlusExpr implements Expr { ... }
public final class TimesExpr implements Expr { ... }
public final class NegExpr implements Expr { ... }
反射 API
我们将以下公共方法添加到 java.lang.Class:
Class<?>[] getPermittedSubclasses()
boolean isSealed()
getPermittedSubclasses() 方法返回一个包含 java.lang.Class 对象的数组,该对象表示该类的允许子类(如果该类是密封的)。如果是非密封的类,则返回一个空数组。
如果给定的类或接口是密封的,则方法 isSealed 返回 true。
密封类的高级用法
密封类和record类
记录类是隐式final的(不可以用abstract修饰),因此记录类的密封层次结构比上面的数学表达式示例更简洁:
package com.example.expression;
public sealed interface Expr
permits ConstantExpr, PlusExpr, TimesExpr, NegExpr { ... }
public record ConstantExpr(int i) implements Expr { ... }
public record PlusExpr(Expr a, Expr b) implements Expr { ... }
public record TimesExpr(Expr a, Expr b) implements Expr { ... }
public record NegExpr(Expr e) implements Expr { ... }
密封类和类型转换
观察下面的示例:C 是密封的并且有一个permits的直接子类D。根据密封类型的定义,D 必须是最终的、密封的或非密封的。在这个例子中,C 的所有直接子类都是 final 的并且没有实现 I。因此这个程序无法编译,因为不可能有 C 的子类型实现 I。
interface I {}
sealed class C permits D {}
final class D extends C {}
void test (C c) {
if (c instanceof I) // Compile-time error!
System.out.println("It's an I");
}
下面这个程序是正确的,因为非密封类型 D 的子类型可以实现 I。
interface I {}
sealed class C permits D, E {}
non-sealed class D extends C {}
final class E extends C {}
void test (C c) {
if (c instanceof I)
System.out.println("It's an I");
}
密封类和模式匹配
通过 switch (JEP 406) 的模式匹配,编译器可以确认每个允许的 Shape 子类都被覆盖,因此不需要默认子句或其他总模式。
public abstract sealed class Shape
permits Circle, Rectangle, Square{ ... }
Shape rotate(Shape shape, double angle) {
return switch (shape) { // pattern matching switch
case Circle c -> c;
case Rectangle r -> shape.rotate(angle);
case Square s -> shape.rotate(angle);
// no default needed!
}
}
此外,如果缺少以下三种情况中的任何一种,编译器将发出错误消息:‘switch’ statement does not cover all possible input values