2021/09/14,JDK 17的GA(General Availability)版本正式发布,作为JDK 8和JDK 11之后的另一个LTS(long-term support,长期支持)版本,自然新增了很多新特性,今天就来给大家介绍其中之一:Sealed Classes,我把它翻译为密封类

    我们知道在Java中,类是可以继承的,虽然一个类不能有多个父类,但一个类可以有多个子类,但是父类并不能控制子类的个数,只要一个类是可继承的(非final),那么我们就可以定义多个子类来继承这个父类。

    这种机制是灵活的,但是对于父类来说则失去了控制权,换个角度理解,一个父类不能知道并控制哪些类继承了它,作为父类只能被子类任意继承与使用,对于父类来说是不安全的。

    Sealed Classes起到的作用就是,如果一个类是Sealed的,那么该类就可以指定有哪些子类可以继承该父类,Demo如下。

    有一个Car类:

    1. public class Car {
    2. }

    我们可以加上sealed关键字。

    1. public sealed class Car {
    2. }

    此时会编译报错:Sealed class must have subclasses
    意思是,Sealed class必须有子类,那么我们来新建一个Car的子类RedCar:

    1. public class RedCar extends Car{
    2. }

    建之后,Car类依然报错:Sealed class permits clause must contain all subclasses
    意思是Sealed class的permits clause必须包含所有子类,这是定义Sealed class的另外一步,如果一个类是Sealed的,那么就需要指定哪些子类可以继承它,所以Car类的代码改成这样:

    1. public sealed class Car permits RedCar {
    2. }

    不过,改完之后,依然会报错:All sealed class subclasses must either be final, sealed or non-sealed
    意思是所有sealed类的子类必须是final、sealed、non-sealed三者之一。
    所以,我们把RedCar类改成:

    1. public final class RedCar extends Car{
    2. }

    这样代码才完全无错,根据上述步骤可以总结得到:

    一个类(A类)被sealed修饰时,需要通过关键字permits来指定该类的子类(A1),意思是A类只能被A1继承,其他类不能继承A类。

    比如创建一个类BlueCar:

    1. public class BlueCar extends Car{
    2. }

    会报错:BlueCar is not allowed in the sealed hierarchy
    意思是:BlueCar不允许在sealed继承层次中,也就是BlueCar不能继承一个sealed的类
    需要在Car的permits列表中添加BlueCar,并且还需要在BlueCar类中使用final、sealed、non-sealed三者之一,就像RedCar一样,比如:

    1. public non-sealed class BlueCar extends Car{
    2. }

    目前Car类只能允许有两个子类:RedCar, BlueCar。这就是密封,Car类被密封了,只允许有这两个子类。
    另外对于sealed类的子类,需要被final、sealed、non-sealed三者之一修饰,这三者的意思是:

    1. final,表示该子类不能被继续继承
    2. sealed,表示该子类也是一个密封类,也需要指定permits
    3. non-sealed,表示该子类不是一个密封类,可以被任意继承

    现在三个类是在单独的三个文件中,不过我们也可以这么写:

    1. public sealed class Car {
    2. public final class RedCar extends Car{ }
    3. public non-sealed class BlueCar extends Car{ }
    4. }

    直接将RedCar和BlueCar作为Car的内部类,这样就不需要使用permits了,表达的意思是一样的。

    另外sealed可以用在接口上的,表达意思是:该接口只能存在哪些实现类

    sealed用在接口上相比于用在类可能更具有实用价值(也可能是我见识少了),比如我在开发一个功能时,定义了一个接口,我其实只是自己要利用这个接口而已,我自己给这个接口提供了两个实现类,但是我并不想其他类也能来实现这个接口,这样我就可以用sealed来修饰我的接口了,达到了只能我用的控制。

    另外再介绍一个小知识点,是我在看sealed classed的JEP文档时看到的。

    有一个接口I和类C:

    1. public interface I {
    2. }
    3. public class C {
    4. }

    随便写一个方法test:

    1. public void test(C c){
    2. if (c instanceof I) {
    3. System.out.println("It's an I");
    4. }
    5. }

    此时代码编译不会报错,但是我在C类上加上final:

    1. public final class C {
    2. }

    test方法将会出现编译报错:Inconvertible types; cannot cast ‘com.zhouyu.C’ to ‘com.zhouyu.I’

    为什么C类没有final时不报错,加了final后就报错?

    原因是,在C类没有final时,表示C类是可以被继承的,这就有可能会出现一个B类继承了C类,同时实现了接口I,既然有可能出现这种情况,所以Java编译器无法在编译期就认为C类对象无法转成接口I类型的。

    而C类加上final后,就表示C类是不能被继承的,那么就不会出现其他情况了,C类本身没有实现接口I,那就不会有C类对象能转成接口I类型,所以编译器能直接识别出来并报错。

    好了,本篇文章就先写到这,后续继续分析其他新特性,好东西需要被更多人知道,大家觉得文章还行就帮忙分享出去,如果有发现抄袭党,就帮忙举报,感谢。