用非密封 class 扩展密封 class 有什么意义?

What is the point of extending a sealed class with a non-sealed class?

我不太明白为什么JEP 360/Java15中有一个non-sealed关键字。 对我来说,密封的 class 的扩展应该只是最终的或密封的 class 本身。

提供“非密封”关键字将邀请开发人员进行黑客攻击。 为什么我们允许将密封 class 扩展为非密封 class?

假设您想在代码中写一个 class Shape。如果你不想实例化它,你也可以将它设为 class abstract。你想在一些 class 中扩展它,比如 CircleTriangleRectangle。现在您已经实现了它,您希望确保没有人能够扩展您的 Shape class。不用 sealed 关键字可以吗?
不,因为您必须将其设为 final,在这种情况下您将无法将其扩展为具有任何子 class。这就是 sealed 关键字的用武之地!您制作了一个抽象 class 密封并限制了 classes 能够扩展它的内容:

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

请记住,如果您的子class与Shape class不在同一个包中,您必须在包中提及他们的名字:

public abstract sealed class Shape
    permits com.example.Circle    { ... }

现在,当您声明这三个子class时,您必须将它们设为 finalsealednon-sealed (一个和只能使用这些修饰符之一).

现在什么时候可以在需要的 class 中扩展 Circle?只有当你告诉 Java 它可以通过使用 non-sealed 关键字被未知的子 class 扩展:

public non-sealed class Circle {...} 

我认为 JEP 360 中的以下示例说明了这一点:

package com.example.geometry;

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

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 non-sealed class Square extends Shape {...}

您只想允许指定的 class 扩展 Shape。现在制作 Square non-sealed 有什么意义?因为您想允许任何其他 class 扩展 Square(和层次结构)。

可以这样想:任何想要扩展 Shape 的 class 都必须使用 CircleRectangleSquare 来实现介于之间。因此,此 sub-hierarchy 的每个扩展 class 将是 CircleRectangleSquare(is-a 关系)。

sealed/non-sealed-组合允许您仅“密封”层次结构的一部分,而不是全部(从根开始)。


注意 JEP 360 告诉我们关于允许的 classes:

Every permitted subclass must choose a modifier to describe how it continues the sealing initiated by its superclass:

选项有:finalsealednon-sealed。你被逼得露骨了,所以我们需要non-sealed来“打破封印”。


Brian Goetz 有 一个现实的用例,并解释了现实生活中的好处。我想再补充一个例子:

假设您正在开发一款有英雄和怪物的游戏。有些 classes 可能是这样的:

public sealed class Character permits Hero, Monster {}

public sealed class Hero extends Character permits Jack, Luci {}
public non-sealed class Monster extends Character {}

public final class Jack extends Hero {}
public final class Luci extends Hero {}

游戏有两个主角,还有几个敌人。主要角色是一成不变的 可以有任意多不同的怪物。 游戏中的每个角色要么是英雄,要么是怪物。

这是一个最小的例子,希望它更具说明性,并且可能会有变化,例如添加 class CustomHero 使模组制作者能够创建自定义英雄。

因为在 real-world API 中,有时我们希望支持特定的扩展点,同时限制其他扩展点。 Shape 示例并不是特别令人回味,这就是为什么允许它看起来很奇怪的原因。

Sealed 类 是关于更好地控制 可以扩展给定的可扩展类型。您可能想要这样做有多种原因,“确保没有人扩展层次结构”只是其中之一。

在很多情况下,API 有几个“内置”抽象,然后是一个“逃生舱口”抽象;这允许 API 作者将 would-be 扩展者引导到为扩展而设计的逃生口。

例如,假设您有一个使用 Command 模式的系统,您想要控制几个 built-in 命令的实现,还有一个 UserPluginCommand 用于扩展:

sealed interface Command
    permits LoginCommand, LogoutCommand, ShowProfileCommand, UserPluginCommand { ... }

// final implementations of built-in commands

non-sealed abstract class UserPluginCommand extends Command {
    // plugin-specific API
}

这样的层次结构完成了两件事:

  • 所有的扩展都是通过UserPluginCommand汇集的,可以针对扩展进行防御性设计,提供适合用户扩展的API,但我们仍然可以使用interface-based我们设计中的多态性,知道完全不受控制的子类型是不会出现的;

  • 系统仍然可以依赖于四种允许的类型涵盖 Command 的所有实现这一事实。因此内部代码可以使用模式匹配并对其详尽性充满信心:

switch (command) {
    case LoginCommand(...): ... handle login ...;
    case LogoutCommand(...): ... handle logout ...;
    case ShowProfileCommand(...): ... handle query ...;
    case UserPluginCommand uc: 
        // interact with plugin API
    // no default needed, this switch is exhaustive

UserPluginCommand 的子类型可能不计其数,但系统仍然可以自信地推断它可以用这四种情况覆盖海滨。

将在 JDK 中利用这一点的 API 的一个示例是 java.lang.constant,其中有两个为扩展而设计的子类型——动​​态常量和动态调用站点。

根据此documentationnon-sealed class 允许向世界开放部分继承层次结构。这意味着根 sealed class 只允许一组封闭的子 class 扩展它。

但是,subclasses 仍然可以使用 non-sealed 关键字允许自己被任意数量的 subclasses 扩展。

public sealed class NumberSystem
    // The permits clause has been omitted
    // as all the subclasses exists in the same file.
{ }
final class Binary extends NumberSystem { .. }

final class Octal extends NumberSystem { .. }

final class HexaDecimal extends NumberSystem { .. }

non-sealed class Decimal extends NumberSystem { .. }

final class NonRecurringDecimal extends Decimal {..}
final class RecurringDecimal extends Decimal {..}