360: | Sealed Classes (Preview) 封闭类(预览) |
总结
使用密封类和接口增强Java编程语言。密封类和接口限制了哪些其他类或接口可以扩展或实现它们。这是JDK 15中的预览语言功能。
目标
允许类或接口的作者控制负责实现它的代码。
提供一种比访问修饰符更具声明性的方式来限制超类的使用。
通过支持对模式的详尽分析,支持模式匹配的未来方向。
动机
在Java中,类层次结构允许通过继承重用代码:超类的方法可以被许多子类继承(并因此重用)。然而,类层次结构的目的并不总是重用代码。有时,它的目的是对领域中存在的各种可能性进行建模,例如图形库支持的形状类型或金融应用程序支持的贷款类型。当以这种方式使用类层次结构时,限制子类集可以简化建模。
例如,在图形库中,Shape类的作者可能希望只有特定的类可以扩展Shape,因为该库的大部分工作都涉及以适当的方式处理每种形状。作者对处理Shape的已知子类的代码的清晰度感兴趣,对编写代码来防御Shape的未知子类不感兴趣。在这种情况下,允许任意类扩展Shape,从而继承其代码以供重用,并不是我们的目标。不幸的是,Java认为代码重用始终是一个目标:如果Shape可以扩展,那么它可以扩展任意数量的类。放宽这一假设会有所帮助,这样作者就可以声明一个不允许任意类扩展的类层次结构。在这样一个封闭的类层次结构中,代码重用仍然是可能的,但在更大的范围内是不可能的。
Java开发人员熟悉限制子类集的想法,因为它经常出现在API设计中。该语言在这方面提供了有限的工具:要么将类设为final,这样它就没有子类,要么将类或其构造函数包设为私有,这样它只能在同一个包中有子类。JDK中出现了一个包私有超类的示例:
package java.lang;abstract class AbstractStringBuilder {...}
public final class StringBuffer extends AbstractStringBuilder {...}
public final class StringBuilder extends AbstractStringBuilder {...}
当目标是代码重用时,包私有方法很有用,例如AbstractStringBuilder的子类共享其代码进行追加。然而,当目标是对替代方案进行建模时,这种方法是无用的,因为用户代码无法访问关键抽象(超类)以进行切换。不允许用户在不扩展超类的情况下访问超类是不可能的。(即使在声明Shape及其子类的图形库中,如果只有一个包可以访问Shape,那也会很不幸。)
总之,超类应该可以广泛访问(因为它代表了用户的一个重要抽象),但不能广泛扩展(因为它的子类应该仅限于作者已知的子类)。这样的超类应该能够表示它是与一组给定的子类共同开发的,既可以为读者记录意图,也可以允许Java编译器执行。同时,超类不应过度约束其子类,例如强制它们为最终类或阻止它们定义自己的状态。
示例
public abstract sealed class Shape permits Circle, Rectangle, Square {
}
public non-sealed class Square extends Shape {void side() {System.out.println("边长");}
}
public non-sealed class Rectangle implements Shape {void length() {System.out.println("长度");}
}
public non-sealed class Circle extends Shape {void center() {System.out.println("圆心");}
}
public static void fun(Shape shape){switch (shape){case Circle c -> c.center();case Rectangle r -> r.length();case Square s -> s.side();default -> throw new IllegalStateException("Unexpected value: " + shape);}}public static void main(String[] args) {Shape s1= new Circle();Shape s2= new Rectangle();Shape s3= new Square();fun(s1);fun(s2);fun(s3);}
注意
使用sealed关键字声明封闭类或封闭接口
使用permits关键字允许声明哪个类或接口可以成为其子类或子接口
sealed修饰过的类或接口必须有类继承或实现
sealed也可以修饰接口interface
继承或者实现被sealed修饰过的类或接口,必须用sealed(密封),final(最终),non-sealed(非密封)修饰