为什么此代码中的枚举开关需要默认值?

通常,switch语句中不需要default。 但是,在以下情况下,代码只有在取消注释默认语句时才能成功编译。 任何人都可以解释原因吗?

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

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

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

从技术上讲,我认为真正的原因是JVM字节码validation器总是拒绝函数,其中有一些控制路径从函数的末尾掉落(参见JVM规范的 §4.9.2),所以如果代码是编译它只会在运行时被JVM拒绝。 因此,编译器会向您报告错误,以报告存在问题。

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

“如果满足以下所有条件,则在切换语句后指定V [un]:

  • 交换机块中有一个默认标签,或者在交换机表达式后指定了V [un]。

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

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


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

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

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

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

注意: 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; } 

避免返回的唯一方法是抛出exception。

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

有一个契约,该方法必须返回一个String,除非它抛出exception。 并且每次都不限于xyz的值等于XVZ.AXYZ.B

这是另一个例子,它是obviuos ,代码将运行正确但我们有一个编译时错误,原因相同:

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

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

 default: throw new AssertionError(); 

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