为什么 String switch 语句不支持空格?

我只是想知道为什么 Java7switch语句不支持 null而抛出 NullPointerException?见下面的注释行(取自 关于 switch的 Java 教程文章的例子) :

{
String month = null;
switch (month) {
case "january":
monthNumber = 1;
break;
case "february":
monthNumber = 2;
break;
case "march":
monthNumber = 3;
break;
//case null:
default:
monthNumber = 0;
break;
}


return monthNumber;
}

这将避免在每次使用 switch之前进行 null 检查的 if条件。

70943 次浏览

作为 damryfbfnetsi 指出的评论,JLS 14.11有以下注意事项:

禁止使用 null作为开关标签可以防止编写永远不能执行的代码。如果 switch表达式是引用类型,即 String或装箱的基元类型或枚举类型,则如果表达式在运行时计算为 null,则将发生运行时错误。根据 Java 编程语言的设计者的判断,这比默默地跳过整个 ABC1语句或选择在 default标签(如果有的话)之后执行语句(如果有的话)要好得多。

(强调我的)

虽然最后一句跳过了使用 case null:的可能性,但它似乎是合理的,并提供了对语言设计者的意图的看法。

如果我们更愿意看看实现细节,Christian Hujer 的 这篇博文对于为什么 null不允许在交换机中使用(尽管它集中在 enum交换机而不是 String交换机上)有一些有见地的推测:

在底层,switch语句通常会编译成 tablesswitch 字节码。而 switch的“物理”论点以及它的案例都是 int。要打开的 int 值是通过调用方法 Enum.ordinal()确定的。序数从零开始。

这意味着,将 null映射到 0不是一个好主意。第一个枚举值上的开关将与 null 无法区分。也许从1开始计算枚举的序数是个好主意。然而,它还没有被这样定义,而且这个定义不能被改变。

String开关 实施方式不同时,enum开关首先出现,并且开创了当参考是 null时如何开关参考类型的先例。

答案很简单,如果使用具有引用类型(例如装箱的基元类型)的开关,则如果表达式为 null,则将发生运行时错误,因为取消装箱将引发 NPE。

所以 case null (这是非法的)无论如何都不会被执行;)

根据 Java 文档:

开关使用 byte、 short、 char 和 int 原始数据 它还可以处理枚举类型(在枚举类型中讨论) , String 类,以及一些特殊的类,它们包装了某些 基元类型: 字符、字节、短数和整数(在 数字和字符串)。

因为 null没有类型,也不是任何事物的实例,所以它不能与 switch 语句一起工作。

这是试图回答它为什么抛出 NullPointerException

下面的 javap 命令的输出显示,case是根据 switch参数字符串的散列码选择的,因此在对 null 字符串调用 .hashCode()时抛出 NPE。

6: invokevirtual #18                 // Method java/lang/String.hashCode:()I
9: lookupswitch  { // 3
-1826660246: 44
-263893086: 56
103666243: 68
default: 95
}

这意味着,基于对 Java 的 hashCode 能为不同的字符串产生相同的值吗?的回答,虽然很少,但仍然有可能匹配两种情况(两个具有相同哈希代码的字符串)参见下面的示例

    int monthNumber;
String month = args[0];


switch (month) {
case "Ea":
monthNumber = 1;
break;
case "FB":
monthNumber = 2;
break;
// case null:
default:
monthNumber = 0;
break;
}
System.out.println(monthNumber);

为什么

  10: lookupswitch  { // 1
2236: 28
default: 59
}
28: aload_3
29: ldc           #22                 // String Ea
31: invokevirtual #24                 // Method java/lang/String.equals:(Ljava/lang/Object;)Z
34: ifne          49
37: aload_3
38: ldc           #28                 // String FB
40: invokevirtual #24                 // Method java/lang/String.equals:(Ljava/lang/Object;)Z
43: ifne          54
46: goto          59 //Default

正如您可以看到的,只为 "Ea""FB"生成一个 case,但是使用两个 if条件来检查每个 case 字符串是否匹配。实现此功能的非常有趣和复杂的方法!

一般来说,null处理起来很麻烦; 也许没有 null,一种更好的语言也能生存。

你的问题可以通过

    switch(month==null?"":month)
{
...
//case "":
default:
monthNumber = 0;


}

这并不好看,但是 String.valueOf()允许您在开关中使用空字符串。如果它找到 null,它会将其转换为 "null",否则它只返回传递给它的同一个 String。如果您不显式地处理 "null",那么它将转到 default。唯一需要注意的是,无法区分字符串 "null"和实际的 null变量。

    String month = null;
switch (String.valueOf(month)) {
case "january":
monthNumber = 1;
break;
case "february":
monthNumber = 2;
break;
case "march":
monthNumber = 3;
break;
case "null":
monthNumber = -1;
break;
default:
monthNumber = 0;
break;
}
return monthNumber;

长话短说... (希望够有趣! ! !)

Enum 最初是在 Java1.5(二零零四年九月)中引入的,而向 允许字符串开关请求的 臭虫被归档为长回(95年10月)。如果你看看在 二零零四年六月上发表的关于这个 bug 的评论,它说 Don't hold your breath. Nothing resembling this is in our plans.看起来像是他们推迟了(被忽略了)这个 bug,最终在同一年发布了 Java 1.5,他们引入了序数从0开始的‘ enum’,并决定(没打中)不支持枚举的 null。在后来的 Java1.7(Java1.50)中,他们遵循(Java1.51)与 String 相同的原理(即在生成字节码时,在调用 hashcode ()方法之前不执行 null 检查)。

所以我认为它归结为一个事实枚举是第一个出现的并且实现了它的序数从0开始,因为它不能在 switch 块中支持 null 值,后来他们决定在 String 中使用相同的原理,即在 switch 块中不允许 null 值。

使用 String,他们可以在实现 Java 代码到字节码的转换时处理 NPE (由于尝试为 null 生成 hashcode 而引起) ,但最终决定不这样做。

参考: 该 BUG , JavaVersionHistory , JavaCodeToByteCode , 那么

在@Paul Bellora 的回答中,我同意 https://stackoverflow.com/a/18263594/1053496中有深刻见解的评论。

我从我的经历中又找到了一个原因。

如果‘ case’可以为 null,这意味着 switch (变量)为 null,那么只要开发人员提供匹配的‘ null’case,那么我们就可以认为它没问题。但是,如果开发人员不提供任何匹配的“ null”情况会发生什么。然后,我们必须将其与“默认”情况相匹配,这可能不是开发人员打算在默认情况下处理的情况。因此,将“ null”匹配到默认值可能会导致“令人惊讶的行为”。 因此抛出“ NPE”将使开发人员显式地处理每个案例。我发现在这种情况下扔 NPE 非常周到。

使用 ApacheStringUtils 类

String month = null;
switch (StringUtils.trimToEmpty(month)) {
case "xyz":
monthNumber=1;
break;
default:
monthNumber=0;
break;
}