总结:Java 17 新特性 - Sealed Class

本文深入解析Java 409 JEP中的密封类,涵盖语法结构、子类约束、可访问性、接口应用、反射API、与record类及类型转换的关系,以及模式匹配在实际开发中的使用实例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

本文是对官方文档(https://openjdk.org/jeps/409)的概括总结,更详细的内容请看官方文档的不完全翻译(https://blog.youkuaiyun.com/weixin_38833041/article/details/125398354

简介

密封的类和接口限制了哪些其他类(子类)或接口可以扩展或实现它们(父类、父接口),更细致的限制了超类的使用。

密封类的语法

类声明语法

密封类或接口只能由允许的类和接口扩展或实现。使用sealed修饰符来声明密封类。然后,在extends和implements之后,使用permits指定允许扩展密封类的类。

NormalClassDeclaration:
  {ClassModifier} class TypeIdentifier [TypeParameters]
    [Superclass] [Superinterfaces] [PermittedSubclasses] ClassBody

ClassModifier:
  (one of)
  Annotation public protected private
  abstract static sealed final non-sealed strictfp

PermittedSubclasses:
  permits ClassTypeList

ClassTypeList:
  ClassType {, ClassType}

例如:

package com.example.geometry;

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

当permits的子类的大小和数量较小时,可以将它们声明在与密封类相同的源文件中,同时可以省略permits语句,Java 编译器将从源文件中的声明推断出permits的子类。permits 指定的类必须有一个规范的名称,否则会报告编译时错误。这意味着匿名类和局部类不能成为密封类的子类型。

abstract sealed class Root { ... 
    final class A extends Root { ... }
    final class B extends Root { ... }
    final class C extends Root { ... }
}

密封类对子类的约束

  1. 密封类及其允许的子类必须属于同一个模块,并且如果在未命名的模块中声明,则必须属于同一个包。
  2. 每个允许的子类都必须直接继承密封类。
  3. 每个允许的子类都必须使用final、sealed或non-sealed修饰符中的一个来描述:
    • 允许的子类声明为 final 表示它无法再扩展。
    • 允许的子类声明为sealed表示它可以以一种受限制的方式进一步扩展
    • 允许的子类声明为non-sealed表示它可以任意的扩展

作为第三个约束的示例,Circle 和 Square 是 final 而 Rectangle 是密封的,WeirdShape是非密封的:

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

密封类的可访问性

允许的子类和它的密封超类必须可以相互访问,因为 extends 和 permit 子句使用类名。但是,permits的子类不需要彼此具有相同的可访问性,也不必与密封类具有相同的可访问性。

密封接口

和密封类相似,可以使用sealed 修饰符来密封接口,使用permits指定允许的实现类和子接口。

例如下面是一个类层次结构的经典示例:建模数学表达式。

package com.example.expression;

public sealed interface Expr
    permits ConstantExpr, PlusExpr, TimesExpr, NegExpr { ... }

public final class ConstantExpr implements Expr { ... }
public final class PlusExpr     implements Expr { ... }
public final class TimesExpr    implements Expr { ... }
public final class NegExpr      implements Expr { ... }

反射 API

我们将以下公共方法添加到 java.lang.Class:

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

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

如果给定的类或接口是密封的,则方法 isSealed 返回 true。

密封类的高级用法

密封类和record类

记录类是隐式final的(不可以用abstract修饰),因此记录类的密封层次结构比上面的数学表达式示例更简洁:

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

密封类和类型转换

观察下面的示例:C 是密封的并且有一个permits的直接子类D。根据密封类型的定义,D 必须是最终的、密封的或非密封的。在这个例子中,C 的所有直接子类都是 final 的并且没有实现 I。因此这个程序无法编译,因为不可能有 C 的子类型实现 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");
}

下面这个程序是正确的,因为非密封类型 D 的子类型可以实现 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");
}

密封类和模式匹配

通过 switch (JEP 406) 的模式匹配,编译器可以确认每个允许的 Shape 子类都被覆盖,因此不需要默认子句或其他总模式。

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

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

此外,如果缺少以下三种情况中的任何一种,编译器将发出错误消息:‘switch’ statement does not cover all possible input values

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值