建议

本文档仅仅记录自己的一些片面认知,具体文档说明请参考官方地址:http://openjdk.java.net/projects/jdk/17/

Sealed修饰符

作用域:类、抽象类、接口。不可作用域内部类上

目标:声明一个类或为密封类,只有指定的类才可以继承改类。声明一个接口为密封接口,只有指定的接口可以继承该接口,只有指定的类可以实现该接口。

Sealed classes

  • 子类与父类在同一包下声明方式
package com.example.geometry;

public abstract sealed class Shape
    permits Circle, Rectangle, Square { ... }
  • 子类与父类在统一模块下声明方式
package com.example.geometry;

public abstract sealed class Shape 
    permits com.example.polar.Circle,
            com.example.quad.Rectangle,
            com.example.quad.simple.Square { ... }
  • 子类与父类在同一 .java 文件下声明方式
abstract sealed class Root { ... 
    final class A extends Root { ... }
    final class B extends Root { ... }
    final class C extends Root { ... }
}

声明注意:指定的类permits必须具有规范名称,否则会报告编译时错误。这意味着匿名类和本地类不能成为密封类的子类型。

声明效果:

  1. 密封类及其允许的子类必须属于同一个模块,并且如果在未命名的模块中声明,则属于同一个包

  2. 每个允许的子类必须为final、sealed,或non-sealed

  3. 每个允许的子类必须使用修饰符来描述它如何传播由其超类发起的密封:

    • 可以声明允许的子类final以防止其在类层次结构中的部分被进一步扩展。(Record classes是隐式声明的final。)
    • 一个允许的子类可以被声明sealed为允许它的层次结构部分比其密封超类所设想的更进一步,但以一种受限的方式。
    • 可以声明一个允许的子类,non-sealed以便它的层次结构部分恢复为对未知子类的扩展开放。密封类不能阻止其允许的子类这样做。(修饰符non-sealed是 为 Java 提议的第一个带连字符的关键字。)
    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 { ... }  
    

Sealed interfaces

sealed修饰接口

sealed interface Celestial 
    permits Planet, Star, Comet { ... }

final class Planet implements Celestial { ... }
final class Star   implements Celestial { ... }
final class Comet  implements Celestial { ... }

sealingrecord classes使用

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 { ... }

范围特性

该修饰符的出现是为了解决java类型范围不标准的问题,例如,如下代码是可以正常编译的,及时我们可以看出test方法没有意义。

interface I {}
class C {} // does not implement I

void test (C c) {
    if (c instanceof I) 
        System.out.println("It's an I");
}

在过去,我们还可以通过使用final关键字一把切,例如下面这样就会编译报错,但是我们会发现I接口就没有任何人可以取实现他了。

interface I {}
final class C {}

void test (C c) {
    if (c instanceof I)     // Compile-time error!
        System.out.println("It's an I");
}

而我们的sealed修饰符就可以解决这个问题,在下面这个案例中,会检测出CC的所有子类都没有实现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");}

再来一个案例,子类使用non-sealed修饰符修饰。此处non-sealed不是重点,重点是C发现他有个子类实现了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");}

JDK中的sealed使用案例:java.lang.constant.ConstantDesc

package java.lang.constant;public sealed interface ConstantDesc    permits String, Integer, Float, Long, Double,            ClassDesc, MethodTypeDesc, DynamicConstantDesc { ... }// ClassDesc is designed for subclassing by JDK classes onlypublic sealed interface ClassDesc extends ConstantDesc    permits PrimitiveClassDescImpl, ReferenceClassDescImpl { ... }final class PrimitiveClassDescImpl implements ClassDesc { ... }final class ReferenceClassDescImpl implements ClassDesc { ... } // MethodTypeDesc is designed for subclassing by JDK classes onlypublic sealed interface MethodTypeDesc extends ConstantDesc    permits MethodTypeDescImpl { ... }final class MethodTypeDescImpl implements MethodTypeDesc { ... }// DynamicConstantDesc is designed for subclassing by user codepublic non-sealed abstract class DynamicConstantDesc implements ConstantDesc { ... }

Sealed修饰与类型匹配

  • 旧有方式
Shape rotate(Shape shape, double angle) {        if (shape instanceof Circle) return shape;        else if (shape instanceof Rectangle) return shape;        else if (shape instanceof Square) return shape;        else throw new IncompatibleClassChangeError();}
  • 新方式
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!    }} 

java类声明的语法修改如下:

NormalClassDeclaration:  {ClassModifier} class TypeIdentifier [TypeParameters]    [Superclass] [Superinterfaces] [PermittedSubclasses] ClassBodyClassModifier:  (one of)  Annotation public protected private  abstract static sealed final non-sealed strictfpPermittedSubclasses:  permits ClassTypeListClassTypeList:  ClassType {, ClassType}

反射相关

java.lang.Class添加了两个关于sealed的API

  • Class<?>[] getPermittedSubclasses()
  • boolean isSealed()

如果该类是密封的,则该方法getPermittedSubclasses()返回一个包含java.lang.Class表示该类允许的子类的对象的数组 。如果类未密封,则返回一个空数组。

isSealed如果给定的类或接口是密封的,则该方法返回 true。(比较isEnum。)

java匹配相关语法优化

从java16开始,如果能判断对象的类型是某个类,那么不需要再进行强转,可以直接使用那个类的特性

// Old codeif (o instanceof String) {    String s = (String)o;    ... use s ...}// New codeif (o instanceof String s) {    ... use s ...}
// TODO:还没有正式发布,很多特性还没有明确下来,idea的语法检测现在也不支持,所以等9.14y