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

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

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


答案 1

因为在实际的 API 中,有时我们希望支持特定的扩展点,同时限制其他扩展点。不过,这些例子并不是特别令人回味,这就是为什么允许它似乎是一件奇怪的事情。Shape

密封类是关于更好地控制可以扩展给定的可扩展类型。您可能希望这样做有几个原因,“确保没有人扩展层次结构”只是其中之一。

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

例如,假设您有一个使用该模式的系统,有几个内置命令,您希望控制其实现,以及一个 for 扩展:CommandUserPluginCommand

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

// final implementations of built-in commands

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

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

  • 所有扩展都是通过 汇集的,这可以防御性地设计为扩展并提供适合用户扩展的API,但是我们仍然可以在设计中使用基于接口的多态性,因为我们知道完全不受控制的子类型不会出现;UserPluginCommand

  • 系统仍然可以依赖于这样一个事实,即四种允许的类型涵盖了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


答案 2

我认为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 {...}

您希望仅允许指定的类进行扩展 。现在制作有什么意义?因为您希望允许任何其他类进行扩展(和层次结构)。ShapeSquarenon-sealedSquare

可以这样想:任何想要扩展的类都必须使用 或介于两者之间。因此,此子层次结构的每个扩展类都将是 一个 或 一个 (is-a 关系)。ShapeCircleRectangleSquareCircleRectangleSquare

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


请注意 JEP 360 告诉我们的有关允许类的内容:

每个允许的子类都必须选择一个修饰符来描述它如何继续由其超类启动的密封:

选项包括:或 。你被迫要明确,所以我们需要“打破封印”。finalsealednon-sealednon-sealed


Brian Goetz发布了一个现实的用例,并解释了现实生活中的好处是什么。我想再添加一个例子:

想象一下,你正在开发一个有英雄和怪物的游戏。有些类可能是这样的:

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

游戏有两个主要角色,有几个敌人。主要角色是一成不变的,但可以有尽可能多的不同怪物,只要你喜欢。游戏中的每个角色要么是英雄,要么是怪物。

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


推荐