用非密封 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 中扩展它,比如 Circle
、Triangle
和 Rectangle
。现在您已经实现了它,您希望确保没有人能够扩展您的 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时,您必须将它们设为 final
、sealed
或 non-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 都必须使用 Circle
、Rectangle
或 Square
来实现介于之间。因此,此 sub-hierarchy 的每个扩展 class 将是 Circle
、Rectangle
或 Square
(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:
选项有:final
、sealed
或non-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
,其中有两个为扩展而设计的子类型——动态常量和动态调用站点。
根据此documentation,non-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 {..}
我不太明白为什么JEP 360/Java15中有一个non-sealed
关键字。
对我来说,密封的 class 的扩展应该只是最终的或密封的 class 本身。
提供“非密封”关键字将邀请开发人员进行黑客攻击。 为什么我们允许将密封 class 扩展为非密封 class?
假设您想在代码中写一个 class Shape
。如果你不想实例化它,你也可以将它设为 class abstract
。你想在一些 class 中扩展它,比如 Circle
、Triangle
和 Rectangle
。现在您已经实现了它,您希望确保没有人能够扩展您的 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时,您必须将它们设为 final
、sealed
或 non-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 都必须使用 Circle
、Rectangle
或 Square
来实现介于之间。因此,此 sub-hierarchy 的每个扩展 class 将是 Circle
、Rectangle
或 Square
(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:
选项有:final
、sealed
或non-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
,其中有两个为扩展而设计的子类型——动态常量和动态调用站点。
根据此documentation,non-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 {..}