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

时间:2011-02-16 06:28:42

标签: 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";
    }
}

8 个答案:

答案 0 :(得分:41)

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

我相信(而且我不确定)这样做的原因是,即使您覆盖了所有enum个案例,代码在某些情况下仍可能失败。特别是,假设您编译包含此switch语句的Java代码(工作正常),然后稍后更改enum以便现在有第三个常量 - 让我们说C - 但是你不要不要使用switch语句重新编译代码。现在,如果您尝试编写使用以前编译的类并将C传递到此语句中的Java代码,那么代码将没有返回值,违反了所有函数始终返回值的Java契约。

从技术上讲,我认为真正的原因是JVM字节码验证器总是拒绝某些函数,在这些函数中有一些控制路径从函数的末尾掉落(参见§4.9.2of the JVM spec),并且因此,如果代码是编译它,那么JVM在运行时也会被拒绝。因此,编译器会向您报告错误,以报告存在问题。

答案 1 :(得分:39)

我认为这可以通过switch语句(JLS 16.2.9)的JLS明确赋值规则来解释,其中说明了以下内容:

  

“如果满足以下所有条件,则在切换语句后为[未]分配:

     
      
  • 交换机块中有一个默认标签,或者在切换表达式后为[un]分配了V.
  •   

如果我们将此应用于作为方法返回值的名义V,我们可以看到,如果没有default分支,则该值将在概念上取消分配。

好的......我正在推断明确的分配规则以涵盖返回值,也许他们没有。但事实上我在规范中找不到更直接的东西并不意味着它不存在: - )


编译器必须提供错误的另一个(更合理的)原因。它源于enumJLS 13.4.26)的二进制兼容性规则,其中规定了以下内容:

  

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

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


在Java 12中,他们引入了包括切换表达式的切换增强功能。对于在编译时和运行时之间更改的枚举,这会遇到同样的问题。根据{{​​3}},他们解决了这个问题如下:

  

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

     

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

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

答案 2 :(得分:7)

如前所述,您需要返回一个值,并且编译器不会假设enum以后不能更改。例如。你可以创建另一个版本的枚举,并在不重新编译方法的情况下使用它。

注意:xyz的第三个值为null。

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);
}

答案 3 :(得分:1)

有一个合同,这个方法 返回一个String,除非它抛出异常。每次 并不仅限于xyz的值等于XVZ.AXYZ.B的情况。

这是另一个例子,它是 obviuos ,代码将运行正确,但出于同样的原因我们遇到编译时错误:

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

必须添加默认语句,这是真的,您必须随时返回一个值。因此,要么在切换块之后添加一个默认语句或添加一个return语句。

答案 4 :(得分:1)

在Java 12中,您可以使用预览开关表达式功能(JEP-325),如下所示:

public static String testSwitch(XYZ xyz) {
    return switch (xyz) {
        case A -> "A";
        case B -> "B";
    };
}

只要您处理switch中的所有枚举值,就不需要默认大小写。

请注意,要使用预览功能,您必须将--enable-preview --source 12选项传递给javacjava

答案 5 :(得分:0)

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

答案 6 :(得分:0)

default: throw new AssertionError();

答案 7 :(得分:0)

在代码示例中xyz为null时会发生什么?在这种情况下,该方法缺少return语句。