Java17中封类的是什么?

What are sealed classes in Java 17?

今天,我将我的Java版本从16更新到17,我发现sealed类是其中的一个新特性。我认为可以这样声明:

public sealed class Main permits AClass, AnotherClass {
}

但是,Java中密封的类有什么用呢?

I also knew that it was a preview feature in jdk-15.

You can follow this link for examples.

简而言之,sealed 类 让您可以控制哪些模型,类 等可以实现或扩展 class/interface。

来自 link 的示例:

public sealed interface Service permits Car, Truck {

    int getMaxServiceIntervalInMonths();

    default int getMaxDistanceBetweenServicesInKilometers() {
    return 100000;
  }
}

该接口只允许 Car 和 Truck 实现它。

JEP 409解释为

A sealed class or interface can be extended or implemented only by those classes and interfaces permitted to do so.

更实用的解释如下:

过去的情况是:

  • 您无法限制一个接口被另一个接口扩展
  • 您无法限制哪些 class 能够实现特定接口。
  • 您必须将 class 声明为 final,以免被另一个 class 扩展。这样 class 就无法扩展已声明的 final class。这是非黑即白的方法。

目前sealed关键字的情况是:

  • 您现在可以限制一个接口被其他接口扩展,并为仅允许扩展它的某些特定接口制定规则。

    示例:

    public sealed interface MotherInterface permits ChildInterfacePermitted {}
    
    //Has to be declared either as sealed or non-sealed
    public non-sealed interface ChildInterfacePermitted extends MotherInterface {}  
    
    public interface AnotherChildInterface extends MotherInterface {} 
    //compiler error! It is not included in the permits of mother inteface
    
  • 您现在可以创建一个接口并且 select 仅允许特定的 class 实现该接口。不允许所有其他 class 实施它。

    示例:

     public sealed interface MotherInterface permits ImplementationClass1 {} 
    
     //Has to be declared either as final or as sealed or as non-sealed
     public final class ImplementationClass1 implements MotherInterface {} 
    
     public class ImplementationClass2 implements MotherInterface {} 
     //compiler error! It is not included in the permits of mother inteface
    
  • 您现在可以限制 class 被扩展(与之前的 final 相同)但您现在可以允许某些特定的 class 扩展它。所以现在你有更多的控制权,因为关键字 final 是绝对限制每个 class 扩展声明的 final class

    示例:

    public sealed class MotherClass permits ChildClass1 {}
    
    //Has to be declared either as final or as sealed or as non-sealed
    public non-sealed class ChildClass1 extends MotherClass {} 
    
     public class ChildClass2 extends MotherClass {} 
     //compiler error! It is not included in the permits of MotherClass
    

重要提示:

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

    示例:

    假设我们有相同的未命名模块和以下包

      -packageA
         -Implementationclass1.java
      -packageB
         -MotherClass.java
    

       -root
          -MotherClass.java
          -packageA
             -Implementationclass1.java
    

    您将收到错误 Class is not allowed to extend sealed class from another package。因此,如果您有一个未命名的模块,所有参与的 class 密封功能的 es 和接口必须完全放在同一个包中。

  • 每个允许的子class必须直接扩展密封的class。

密封类

  • 密封 class 是一个 class 或接口,它限制其他 class 实体或接口可以扩展它。
  • 密封 classes 和接口表示受限的 class 层次结构,提供对继承的更多控制。
  • 密封class的所有直接子class在编译时都是已知的。 class sealed class 模块编译后不能出现其他子class。
  • 例如, 第三方客户端无法在他们的代码中扩展您的密封 class。因此,密封的 class 的每个实例都有一个来自 编译此 class 时已知的有限集合。

定义密封 类

要密封 class,请将密封修饰符添加到其声明中。然后,在任何 extends 和 implements 子句之后,添加 permits 子句。此子句指定可以扩展密封 class.

的 classes

例如,Shape 的以下声明指定了三种允许的子class,Circle、Square 和Rectangle:

public sealed class Shape
    permits Circle, Square, Rectangle {
}

定义以下三个允许的subclasses,Circle,Square,和Rectangle,在与sealed class相同的模块或相同的包中:

public final class Circle extends Shape {
    public float radius;
}

public non-sealed class Square extends Shape {
   public double side;
}   

public sealed class Rectangle extends Shape permits FilledRectangle {
    public double length, width;
}

Rectangle 还有一个子class, FilledRectangle:

public final class FilledRectangle extends Rectangle {
    public int red, green, blue;
}

对允许子项的限制classes

  • 它们必须在编译时被密封 class 访问。

    比如要编译Shape.java,编译器必须能够 访问所有允许的 classes 形状:Circle.javaSquare.java,以及 Rectangle.java。此外,由于 Rectangle 是 sealed class,编译器还需要访问 FilledRectangle.java.

  • 他们必须直接扩展密封的class。

  • 他们必须恰好具有以下修饰符之一来描述如何 它继续由其 superclass:

    发起的密封
    1. 最终:无法进一步扩展
    2. sealed: 只能通过其允许的 subclasses
    3. 进行扩展
    4. 非密封:可以通过未知子classes扩展;密封的 class 无法阻止其允许的子classes 这样做
  • 例如,允许的 Shape 子class 展示了每个 这三个修饰符:Circle 是最终的,而 Rectangle 是密封的, Square 未密封。

  • 它们必须与密封的class在同一个模块中(如果密封 class 在命名模块中)或在同一个包中(如果密封 class 位于未命名模块中,如 Shape.java 示例中所示)。

例如com.example.graphics.Shape下面声明的,它的permitted subclasses都在不同的包中。此示例仅在 Shape 及其所有允许的子 class 都在同名模块中时才能编译。

package com.example.graphics;

    public sealed class Shape 
        permits com.example.polar.Circle,
                com.example.quad.Rectangle,
                com.example.quad.simple.Square { }

根据此 documentation,密封 classes 和接口限制哪些其他 classes 或接口可以扩展或实现它们。它更像是一种限制使用 superclass 而不是使用访问修饰符的声明方式。

在 Java 中,class 可以是最终的,因此没有其他 classes 可以子class 它。如果 class 不是最终的,那么它对所有其他 class 开放以支持代码可重用性。这样做会引起数据建模问题。

下面的 NumberSystem class 对所有 class 开放,因此任何子class 都可以扩展它。如果您想将此 NumberSystem 限制为一组固定的子classes(二进制、十进制、八进制和十六进制)怎么办?。这意味着您不希望任何其他任意 class 扩展此 NumberSystem class.

class NumberSystem { ... }
final class Binary extends NumberSystem { ... }
final class Decimal extends NumberSystem { ... }
final class Octal extends NumberSystem { ... }
final class HexaDecimal extends NumberSystem { ... }

使用密封 class,您可以通过控制可以扩展它的子class 来实现它,并防止任何其他任意 class 这样做。

所有sealed java classes或接口必须使用permits关键字。例如:

Parent.class:

public sealed class Parent permits Child1, Child2 {
  void parentMethod() {
    System.out.println("from a sealed parent class ");
  }
}

Child1.java:

public final class Child1 extends Parent {
  public static void main(String[] args) {
    Child1 obj = new Child1();
    obj.parentMethod();
  }
}

Child2.java:

public final class Child2 extends Parent {
  public static void main(String[] args) {
    Child2 obj = new Child2();
    obj.parentMethod();
  }
}

Child3.java

public final class Child3 extends Parent {
  public static void main(String[] args) {
    Child3 obj = new Child3();
    obj.parentMethod();
  }
}

这个 Child3 class 代码会抛出一个编译时错误说 扩展密封 class Parent 的类型 Child3 应该是 Parent (permits Child3, 就像 Child1Child2) 允许的子类型。