提问人:Bond - Java Bond 提问时间:11/15/2023 更新时间:11/18/2023 访问量:106
何时调用密封层次结构的交换机中的默认情况
When would default case in switch for sealed hierarchy be invoked
问:
给定如下层次结构sealed
sealed interface Shape permits Rectangle, Square
record Rectangle() implements Shape
record Square() implements Shape
由于 & 是记录,它本质上使整个层次结构不可扩展,即不再允许使用子类。Rectangle
Square
从 JDK 21 开始,通过覆盖所有可能或提供案例来覆盖其余部分,强制 switch 的模式匹配是详尽的。switch
case
default
因此,在什么情况下,由于涵盖了所有可能的组合,因此在什么情况下会执行这种情况,为什么甚至允许这样做?switch
default
switch (shape) {
case Rectangle r -> // do something with r;
case Square sq -> // do something with sq;
case null -> // shape could be null
default -> // WHY is this permitted when all possible cases are covered already??
}
P.S.:密封的层次结构肯定可以进化,但是当这种情况发生时,编译器也会自动标记升级自己。switch
答:
密封的层次结构肯定可以进化,但当这种情况发生时,编译器也会自动标记交换机以进行自我升级。
事实并非如此。您似乎假设 switch 语句和 records/sealed 接口将始终一起编译。当然,这在大多数实际情况下可能是正确的,但并不总是正确的。您可以编译密封的接口和记录,而无需重新编译 switch 语句。
例如,假设这些类型都位于以名称命名的单独 .java 文件中,因此 Shape.java、Rectangle.java、Square.java,而 switch 语句位于 Main.java 的方法中。main
首先,我编译我拥有的所有 Java 源文件,并为每个 .java 文件生成 .class 文件。
然后,假设我将 Shape.java 更改为:
sealed interface Shape permits Rectangle, Square, Triangle
并添加了一个 Triangle .java 文件。
之后,我将只编译记录和密封接口,而不编译 Main.java。这是可能的,因为它们不依赖于Main.java。
最后,我运行.这将运行 Main.class 文件,该文件不知道新类,因为它是在我添加 .java Main
Triangle
Triangle
这是执行分支的地方,或者如果你没有分支,这里是抛出 a 的地方。default
default
MatchException
(请注意,该部分仅适用于 switch 表达式和增强的 switch 语句,如问题中的语句。如果没有大小写匹配,非增强型 switch 语句根本不会执行任何操作。MatchException
另请参见Java 语言规范中的 switch 语句的执行和switch 表达式的运行时计算。
到目前为止,答案大多是正确的,但故事还有更多。
到目前为止给出的简单答案是正确的,即无论编译时类型检查是否详尽,都可能存在与大小写不匹配的运行时值。默认类是允许的,因为它可能被选中(如果不提供,编译器会给你一个引发 MatchException 的合成类。
在编译时详尽的开关在运行时可能不是真正详尽的,主要有两个原因:单独编译和剩余。
其他答复中已充分处理了单独汇编;新的枚举常量和密封类型的新子类型可以在运行时显示,因为可以重新编译层次结构,而无需重新编译其切换。这通常由编译器静默地交给你(让你声明一个只抛出“can't get here”的子句是没有意义的),但如果你愿意,你可以自己处理它。default
第二个原因是余数,这反映了“足够穷尽”的合理含义和实际的穷尽性并不完全一致,如果我们要求开关真正详尽,那么编程将是非常无趣的。
一个简单的例子是这样的:
Box<Box<String>> bbs = ...
switch (bbs) {
case Box(Box(String s)): ...
}
这个开关应该打开吗?事实证明,在运行时有一个可能的值不匹配:。(回想一下,嵌套模式与外部模式匹配,然后使用外部模式的绑定作为内部模式的匹配候选项,并且记录模式不能匹配 null,因为它想要调用组件访问器。Box<Box<String>>
Box(null)
我们可以要求详尽无遗,有一个单独的错误处理案例,但没有人愿意这样做,而且对于不那么琐碎的例子,错误处理会压倒有用的案例。因此,Java 做出了务实的选择,即用“人类”术语来定义开关穷举性——对于合理的类来说,代码似乎是详尽的——并允许“愚蠢”的情况由合成默认值处理。(如果你愿意,你仍然可以自由地明确地处理任何愚蠢的案例。此开关被认为是详尽的,但余数为非空。Box(null)
整个概念在模式:穷举性、无条件性和余数中得到了更详细的解释。
评论
switch
Shape
Shape
i += 0
default