何时调用密封层次结构 switch 中的默认情况

Bon*_*ond 1 java switch-statement java-21

给出sealed如下层次结构

sealed interface Shape permits Rectangle, Square

record Rectangle() implements Shape

record Square() implements Shape
Run Code Online (Sandbox Code Playgroud)

由于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??
    }
Run Code Online (Sandbox Code Playgroud)

PS:密封的层次结构肯定可以进化,但是当这种情况发生时,编译器也会自动标记switch为升级自身。

Bri*_*etz 8

到目前为止,答案大多是正确的,但故事还有更多内容。

到目前为止给出的简单答案是正确的,即无论编译时类型检查是否详尽,都可能存在不匹配任何大小写的运行时值。允许使用默认类,因为它有可能被选择(如果您不提供默认类,编译器会为您提供一个抛出 MatchException 的合成类。)

编译时详尽的开关在运行时可能并非真正详尽的原因有两个:单独编译和剩余。

其他答案中已充分处理了单独编译;新颖的枚举常量和密封类型的新颖子类型可以在运行时出现,因为可以重新编译层次结构而无需重新编译其上的开关。这通常是由编译器默默地为您提供的(没有必要让您声明一个default仅抛出“无法到达此处”的子句),但如果您愿意,您可以自己处理它。

第二个原因是剩余,它反映了“足够穷举”的合理含义和实际的穷举并不完全一致,如果我们要求开关真正穷举,那么编程起来就很无趣了。

一个简单的例子是这样的:

Box<Box<String>> bbs = ...
switch (bbs) { 
    case Box(Box(String s)): ...
}
Run Code Online (Sandbox Code Playgroud)

这个开关应该彻底打开吗Box<Box<String>>?事实证明,在运行时有一个可能的值不匹配:Box(null)。(回想一下,嵌套模式与外部模式匹配,然后使用外部模式的绑定作为内部模式的匹配候选——并且记录模式不能匹配 null,因为它想要调用组件访问器。)

我们可以要求详尽无遗,为 提供一个单独的错误处理案例Box(null),但没有人会喜欢这样,并且如果没有那么简单的示例,错误处理将压倒有用的案例。因此,Java 做出了务实的选择,用“人类”术语定义了 switch 的详尽性(对于合理的类来说似乎是详尽的代码),并允许通过合成默认值来处理“愚蠢”的情况。(如果您愿意,您仍然可以自由地显式处理任何愚蠢的情况。)此开关被认为是详尽的,但具有非空余数。

整个概念在模式:穷举性、无条件性和余数中进行了更详细的解释。