【问题标题】:What are sealed classes in Java 17Java 17 中的密封类是什么
【发布时间】:2021-11-12 01:06:31
【问题描述】:

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

public sealed class Main{

}

但是java中密封类有什么用呢?

我也知道这是 jdk-15 中的预览功能

【问题讨论】:

  • 就在几天前,this article 发布了关于这个主题。它可能会让您对sealed 类有所了解。
  • 套用道格拉斯·亚当斯的话说:“一开始[继承]是[发明]。这让很多人非常愤怒,并被广泛认为是一个糟糕的举动。”
  • 这里有一篇比较权威的文章:infoq.com/articles/java-sealed-classes
  • 这里有2分钟的快速视频讲解youtu.be/SSog4r1IiXU

标签: java class sealed-class java-17 java-sealed-type


【解决方案1】:

You can follow this link for examples.

简而言之,密封类让您可以控制可以实现或扩展该类/接口的模型、类等。

链接示例:

public sealed interface Service permits Car, Truck {

    int getMaxServiceIntervalInMonths();

    default int getMaxDistanceBetweenServicesInKilometers() {
    return 100000;
  }
}

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

【讨论】:

  • 有一个问题让我很困惑。为什么限制实现的数量很有用。它会给编写单元测试带来麻烦,我看不到真正的优势......
  • 我不明白这会使编写单元测试变得更加困难,请详细说明。使用密封类编写可以非常清楚类或接口的意图是什么,并且可以帮助避免在有大量开发人员的大型代码存储库中出现一些不幸的事情。
  • 需要一些变通方法来为密封类创建模拟对象。我明白了,清晰的结构是一件好事,但如果它有用,我们需要一个特殊的关键字。我们已经有了“最终”,目的非常相似……
  • Oracle/JDK 不关心对象是否可以被模拟!接口上的 final 关键字将导致 Oracle 在其现有的“A 类实现 B”的内部实现上工作,这也可能令人困惑。
【解决方案2】:

JEP 409 解释为

密封的类或接口只能通过以下方式扩展或实现 那些允许这样做的类和接口。

更实际的解释如下:

过去的情况是:

  • 您不能限制由另一个接口扩展的接口
  • 您无法限制哪些类能够实现特定接口。
  • 您必须将一个类声明为 final 才能不被另一个类扩展。这样,任何类都不能扩展声明的最终类。这是非黑即白的方法。

密封关键字的现状是:

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

    例子:

    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
    
  • 您现在可以创建接口并仅选择允许的特定类 实现该接口。不允许所有其他类实现它。

    例子:

     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
    
  • 您现在可以限制要扩展的类(与之前的 final 相同),但您现在可以允许某些特定的类对其进行扩展。所以现在你有了更多的控制权,因为关键字 final 绝对限制每个类扩展声明的 final 类

    例子:

    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
    

重要提示:

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

    例子:

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

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

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

    你会得到错误Class is not allowed to extend seal class from another package。因此,如果您有一个未命名的模块,则密封函数的所有参与类和接口都必须完全放在同一个包中。

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

【讨论】:

    【解决方案3】:

    密封类

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

    定义密封类

    要密封一个类,请将密封修饰符添加到其声明中。然后,在任何 extends 和 implements 子句之后,添加 permit 子句。该子句指定了可以扩展密封类的类。

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

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

    在与密封类相同的模块或相同的包中定义以下三个允许的子类:Circle、Square 和 Rectangle:

    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;
    }
    

    矩形还有一个子类,FilledRectangle:

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

    对允许的子类的限制

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

      例如要编译Shape.java,编译器必须能够 访问所有允许的 Shape 类:Circle.javaSquare.javaRectangle.java。另外,因为 Rectangle 是 密封类,编译器也需要访问FilledRectangle.java

    • 它们必须直接扩展密封类。

    • 它们必须具有以下修饰符之一才能描述如何 它继续由其超类发起的密封:

      1. final:无法进一步扩展
      2. 密封:只能由其允许的子类扩展
      3. 非密封:可以被未知子类扩展;密封类 不能阻止其允许的子类这样做
    • 例如,允许的 Shape 子类演示了每个 这三个修饰符:Circle 是最终的,而 Rectangle 是密封的并且 Square 是非密封的。

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

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

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

    【讨论】:

      【解决方案4】:

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

      在 Java 中,一个类可以是 final 的,因此其他类不能继承它。如果一个类不是最终的,那么它对所有其他类开放以支持代码可重用性。这样做会引发数据建模问题。

      下面的 NumberSystem 类对所有类开放,因此任何子类都可以扩展它。如果您想将此 NumberSystem 限制为一组固定的子类(Binary、Decimal、Octal 和 HexaDecimal)怎么办?这意味着您不希望任何其他任意类扩展此 NumberSystem 类。

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

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

      【讨论】:

        【解决方案5】:

        所有密封的java类或接口都必须使用permits关键字这里是例子。

        父类

        public seal class Parent 允许 Child1, Child2 {

        void parentMethod() {
            System.out.println("from a sealed parent class ");
        }
        

        }

        Child1.java

        公共最终类 Child1 扩展父级 {

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

        }

        Child2.java

        public final class Child2 扩展 Parent{

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

        }

        Child3.java

        public final class Child3 扩展 Parent{

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

        }

        这个 Child 3 类代码会抛出一个编译时错误说 扩展密封类 Parent 的类型 Child3 应该是 Parent 的允许子类型(允许 Child3 像 child 1 和 child2)

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2022-01-10
          • 1970-01-01
          • 2014-12-27
          • 2021-01-06
          • 2010-12-18
          • 2017-09-23
          • 1970-01-01
          • 2021-11-05
          相关资源
          最近更新 更多