为什么枚举上的开关需要默认值?

2022-08-31 20:26:37

通常,在 switch 语句中不需要缺省值。但是,在以下情况下,仅当我取消注释默认语句时,代码才会成功编译。谁能解释一下为什么?

public enum XYZ {A,B};
public static String testSwitch(XYZ xyz)
{
    switch(xyz)
    {
    case A:
        return "A";
    case B:
    //default:
        return "B";
    }
}

答案 1

您必须取消注释的原因是您的函数说它返回一个 ,但是如果您只定义了标签,那么如果您传入其他任何内容,则该函数将不会返回值。Java要求所有声明它们返回值的函数实际上在所有可能的控制路径上返回一个值,并且在您的情况下,编译器不相信所有可能的输入都返回了值。defaultStringcaseAB

我相信(我不确定这一点)原因是,即使你涵盖了所有的情况,代码在某些情况下仍然可能失败。特别是,假设您编译包含此switch语句的Java代码(工作正常),然后稍后更改,以便现在有第三个常量 - 比方说 - 但您不会使用其中的语句重新编译代码。现在,如果您尝试编写使用以前编译的类并传入此语句的 Java 代码,则代码将没有要返回的值,这违反了 Java 协定,即所有函数始终返回值。enumenumCswitchC

从技术上讲,我认为真正的原因是JVM字节码验证器总是拒绝那些有一些控制路径从函数末尾脱落的函数(参见JVM规范的§4.9.2),所以如果代码要编译,它无论如何都会在运行时被JVM拒绝。因此,编译器会为您提供错误以报告存在问题。


答案 2

我认为这可以通过语句的JLS确定赋值规则(JLS 16.2.9)来解释,该规则声明如下:switch

“V 在 switch 语句之后 [un]assigned,如果以下所有情况都为真:

  • 开关块中存在默认标签,或者在开关表达式之后 [un]分配 V。

如果我们随后将其应用于名义值,即方法的返回值,我们可以看到,如果没有分支,则该值在名义上是未赋值的。Vdefault

还行。。。我正在推断明确的赋值规则来涵盖返回值,也许它们没有。但是,我无法在规范中找到更直接的东西并不意味着它不存在:-)


编译器必须给出错误还有另一个(更合理的)原因。它源于 (JLS 13.4.26) 的二进制兼容性规则,其中声明如下:enum

“从枚举类型添加或重新排序常量不会破坏与预先存在的二进制文件的兼容性。

那么,在这种情况下,这如何适用呢?假设允许编译器推断OP的示例switch语句总是返回某些内容。如果程序员现在更改 了 添加一个额外的常量,会发生什么情况?根据JLS二进制兼容性规则,我们没有破坏二进制兼容性。然而,包含该语句的方法现在可以(取决于其参数)返回未定义的值。不能允许发生这种情况,因此开关必须是编译错误。enumswitch


在Java 12中,他们引入了对switch的增强,其中包括switch表达式。这与在编译时和运行时之间更改的枚举遇到相同的问题。根据JEP 354,他们按如下方式解决了这个问题:

开关表达式的情况必须是详尽无遗的;对于所有可能的值,必须有一个匹配的开关标签。(显然,switch 语句不需要详尽无遗。

在实践中,这通常意味着需要一个默认条款;但是,对于涵盖所有已知常量的枚举开关表达式,编译器会插入一个默认子句,以指示枚举定义在编译时和运行时之间已更改。依靠这种隐式默认子句插入可以使代码更加健壮;现在,当重新编译代码时,编译器会检查是否显式处理了所有情况。如果开发人员插入了一个显式的默认子句(就像今天的情况一样),则可能的错误将被隐藏。

唯一不清楚的是隐式默认子句实际上会做什么。我的猜测是,它会引发一个未经检查的异常。(截至目前,Java 12 的 JLS 尚未更新以描述新的开关表达式。