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

Java 为什么枚举上的开关需要默认值?,java,enums,default,switch-statement,Java,Enums,Default,Switch Statement,通常,在switch语句中不需要缺省值。但是,在以下情况下,只有在我取消对默认语句的注释时,代码才能成功编译。有人能解释为什么吗 public enum XYZ {A,B}; public static String testSwitch(XYZ xyz) { switch(xyz) { case A: return "A"; case B: //default: return "B"; } } 因为编译器无法猜测

通常,在switch语句中不需要缺省值。但是,在以下情况下,只有在我取消对默认语句的注释时,代码才能成功编译。有人能解释为什么吗

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

因为编译器无法猜测
enum
中只有两个值,并强制您从方法返回值。(但是我不知道为什么它不能猜测,可能它有反射)。

您必须取消对
默认值的注释的原因是您的函数说它返回一个
字符串,但是,如果只为
A
B
定义了
case
标签,那么如果传入任何其他内容,函数将不会返回值。Java要求所有声明返回值的函数实际返回所有可能的控制路径上的值,而在您的情况下,编译器并不确信所有可能的输入都返回了值

我相信(我不确定这一点)这样做的原因是,即使涵盖了所有
enum
情况,代码在某些情况下仍可能失败。特别是,假设您编译了包含这个switch语句的Java代码(它工作得很好),然后稍后更改
enum
,这样现在就有了第三个常量,比如说
C
,但不使用
switch
语句重新编译代码。现在,如果您尝试编写使用先前编译的类并将
C
传入此语句的Java代码,那么代码将不会返回值,这违反了所有函数始终返回值的Java约定


更严格地说,我认为真正的原因是JVM字节码验证器总是拒绝有一些控制路径从函数末尾脱落的函数(参见§4.9.2),因此,如果要编译代码,JVM无论如何都会在运行时拒绝它。因此,编译器会向您报告存在问题的错误。

如前所述,您需要返回一个值,并且编译器不会假设枚举将来无法更改。例如,您可以创建枚举的另一个版本并使用该版本,而无需重新编译该方法

注意:
xyz
有第三个值为空

public static String testSwitch(XYZ xyz) {
    if(xyz == null) return "null";
    switch(xyz){
    case A:
        return "A";
    case B:
        return "B";
    }
    return xyz.getName();
}
这个结果与

public static String testSwitch(XYZ xyz) {
     return "" + xyz;
}
避免返回的唯一方法是抛出异常

public static String testSwitch(XYZ xyz) {
    switch(xyz){
    case A:
        return "A";
    case B:
        return "B";
    }
    throw new AssertionError("Unknown XYZ "+xyz);
}

有一个约定,该方法必须返回字符串,除非它抛出异常。并且每次都不限于
xyz
的值等于
XVZ.A
xyz.B
的情况

下面是另一个示例,其中的obviuos表示代码将正确运行,但由于同样的原因,我们出现了compiletime错误:

public boolean getTrue() {
  if (1 == 1) return true;
}

不必添加默认语句,而是必须随时返回值。因此,在switch块之后添加一个默认语句或一个return语句。

我认为这可以通过JLS针对
switch
语句()的明确分配规则来解释,该规则规定如下:

“如果以下所有条件均为真,则在switch语句后[un]赋值:

  • 开关块中存在默认标签,或者在开关表达式后[un]指定了V
如果我们将其应用于名义上的
V
,这是该方法的返回值,我们可以看到,如果没有
default
分支,则该值名义上是未赋值的

好的……我正在外推确定的赋值规则来覆盖返回值,也许它们没有。但我在规范中找不到更直接的东西并不意味着它不存在:-)


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

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

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


在Java 12中,他们引入了包括开关表达式的开关增强功能。这与在编译时和运行时之间更改的枚举遇到相同的问题。根据,他们解决此问题的方法如下:

public static String testSwitch(XYZ xyz) {
    return switch (xyz) {
        case A -> "A";
        case B -> "B";
    };
}
switch表达式的情况必须是详尽的;对于所有可能的值,必须有一个匹配的switch标签。(显然switch语句不需要是详尽的。)

在实践中,这通常意味着需要一个默认子句;但是,对于覆盖所有已知常量的枚举开关表达式,编译器会插入一个默认子句,以指示枚举定义在编译时和运行时之间发生了更改。依赖此隐式的默认子句插入会导致更多的错误ust code;现在,当重新编译代码时,编译器会检查是否已显式处理所有情况。如果开发人员插入了显式默认子句(如今天的情况),则可能会隐藏错误

唯一不清楚的是隐式default子句实际上会做什么。我猜它会抛出一个未经检查的异常。(到目前为止,Java 12的JLS尚未更新以描述新的开关表达式。)

在Java 12中,您可以
public static String testSwitch(XYZ xyz) {
    return switch (xyz) {
        case A -> "A";
        case B -> "B";
    };
}