为什么String switch语句不支持空的情况?

我只是想知道为什么Java 7的switch语句不支持null情况下,而是抛出NullPointerException ? 请参阅下面的注释行(摘自Java教程文章中关于switch示例):

 { 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之前进行空检查的条件。

正如damryfbfnetsi在评论中指出的那样 , JLS§14.11有如下注释:

禁止使用null作为开关标签可以防止编写永远不能执行的代码。 如果switchexpression式是引用types,即String或盒装原始types或枚举types,则在运行时expression式计算为null ,将发生运行时错误。 在Java编程语言的devise者的判断中,这比静默地跳过整个switch语句或select在default标签(如果有)之后执行语句(如果有的话)更好。

(重点是我的)

尽pipe最后一句跳过了使用case null:的可能性,但这似乎是合理的,并且提供了语言devise者的意图。

如果我们看一下实现的细节,Christian Hujer的这篇博客文章对交换机中不允许使用null (尽pipe它以enum开关而不是String开关为中心)有一些深刻的思考:

在引擎盖下, switch语句通常会编译成一个tableSwitch字节码。 而转换的“物理”论据及其案例也是如此。 通过调用方法Enum.ordinal()来确定打开的int值。 序号从零开始。

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

虽然String开关的实现方式不同 ,但是enum开关首先出现,并设置了引用types为null时如何开启引用types的先例。

一般来说, null是不好处理的; 也许一个更好的语言可以生活没有null

你的问题可能会被解决

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

这并不漂亮,但String.valueOf()允许您在交换机中使用空string。 如果它发现null ,它将它转换为"null" ,否则它只返回你传递的相同的String。 如果你不明确地处理"null" ,那么它将会进入default 。 唯一的警告是,没有办法区分string"null"和实际的空string。

  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; 

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

下面的javap命令的输出显示,基于switch参数string的哈希码来selectcase ,因此当在空string上调用.hashCode()时抛出NPE。

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

这意味着根据Java的hashCode可以为不同的string产生相同的值? 尽pipe很less有匹配的两种情况的可能性(两个具有相同散列码的string),请参见下面的这个例子

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

javap为哪个

  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 

以及你可以看到只有一个案件得到生成,但有两个条件检查马赫与每个案件的string。 非常有趣和复杂的方式来实现这个function!

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

Enum首先在Java1.5 ( 2004年9月 )中引入,并且允许开启String的错误 在1995年 10 被提交。 如果你看看2004年6月发布在这个bug上的评论 ,它说Don't hold your breath. Nothing resembling this is in our plans. Don't hold your breath. Nothing resembling this is in our plans. 看起来他们推迟( 忽略 )这个错误,并最终在同一年推出了Java 1.5,他们引入了从0开始的序号“enum”,并决定不支持枚举null。 后来在Java1.7 ( 2011年7月 ),他们跟随( 强制 )相同的哲学与string(即生成字节码时,在调用hashcode()方法之前没有null检查)。

所以我认为它归结为枚举首先进来,并实现了它的序号0开始,因为他们不能支持空值在切换块和后来与string他们决定强制相同的哲学,即空值不是允许在开关组中。

TL; DR使用string,他们可以照顾NPE(由尝试生成哈希码为null),而实施Java代码到字节码转换,但最后决定不。

参考: TheBUG , JavaVersionHistory , JavaCodeToByteCode , SO

根据Java Docs:

一个开关与byte,short,char和int原始数据types一起工作。 它也适用于枚举types(在枚举types中讨论),String类和几个包装特定基本types的特殊类:Character,Byte,Short和Integer(在Numbers和Strings中讨论)。

由于null没有types,并且不是任何事物的实例,所以不能用switch语句。

答案很简单,如果你使用一个引用types的开关(比如盒装的基本types),如果expression式为空,则会发生运行时错误,因为拆箱会抛出NPE。

所以case null(这是非法的)永远无法执行;)

我同意@Paul Bellora的回答中的有洞察力的评论(隐藏在….) https://stackoverflow.com/a/18263594/1053496

我从我的经验中find了更多的理由。

如果'case'可以为null,这意味着switch(variable)为null,那么只要开发者提供了一个匹配的'null'的情况,那么我们可以认为它是好的。 但是,如果开发人员不提供任何匹配的“空”情况会发生什么。 然后,我们必须将其匹配到“默认”情况下,这可能不是开发人员在默认情况下打算处理的情况。 因此,将“null”与默认值匹配可能会导致“令人惊讶的行为”。 因此抛出“NPE”会使开发者明确地处理每一个案例。 在这种情况下,我发现投放NPE非常周到。