我只是想知道为什么Java 7 switch
语句不支持null
案例而是抛出NullPointerException
?请参阅下面的注释行(示例来自the Java Tutorials article on 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;
}
在每次使用if
之前,这将避免使用switch
条件进行空检查。
正如评论中的damryfbfnetsi points out,JLS §14.11有以下注释:
禁止使用
null
作为开关标签禁止编写永远不会执行的代码。如果switch
表达式是引用类型,即String
或盒装基元类型或枚举类型,那么如果表达式在运行时求值为null
,则会发生运行时错误。在Java编程语言的设计者的判断中,这比静默跳过整个switch
语句或选择在default
标签(如果有)之后执行语句(如果有的话)更好。
(强调我的)
虽然最后一句话超过了使用case null:
的可能性,但这似乎是合理的,并提供了语言设计者的意图。
如果我们宁愿查看实现细节,Christian Hujer的this blog post有一些深刻的猜测,为什么null
不允许在开关中使用(尽管它以enum
开关而不是String
开关为中心):
在引擎盖下,
switch
语句通常会编译为tablesswitch字节代码。而switch
及其案例的“物理”论证是int
s。要打开的int值是通过调用方法Enum.ordinal()
来确定的。 [...]序数从零开始。这意味着,将
null
映射到0
不是一个好主意。第一个枚举值的开关将与null无法区分。也许以1开始计算枚举的序数是个好主意。然而,它没有像那样被定义,并且这个定义不能改变。
当String
切换are implemented differently时,enum
开关排在第一位,并为参考类型在引用为null
时如何切换参考类型设置了先例。
一般情况下,null
很难处理;也许更好的语言可以没有null
生活。
您的问题可能会被解决
switch(month==null?"":month)
{
...
//case "":
default:
monthNumber = 0;
}
它不漂亮,但String.valueOf()
允许您在交换机中使用空字符串。如果它找到null
,它会将其转换为"null"
,否则它只返回传递它的相同String。如果你没有明确地处理"null"
,那么它将转到default
。唯一需要注意的是,无法区分String "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;
NullPointerException
下面的javap命令输出显示case
是根据switch
参数字符串的哈希码选择的,因此当在空字符串上调用.hashCode()
时会抛出NPE。
6: invokevirtual #18 // Method java/lang/String.hashCode:()I
9: lookupswitch { // 3
-1826660246: 44
-263893086: 56
103666243: 68
default: 95
}
这意味着基于Can Java's hashCode produce same value for different strings?的答案虽然罕见,但仍有可能匹配两个案例(两个字符串具有相同的哈希码)请参见下文
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
好吧,你可以看到只有一个案例被生成但有两个if条件来检查每个案例字符串的马赫。实现此功能非常有趣和复杂的方式!
长话短说...(希望足够有趣!)
Enum最初是在Java1.5(2004年9月)推出的,并且bug要求允许切换字符串是在很久以前(95年10月)提交的。如果你看看2004年6月在那个bug上发布的评论,它说Don't hold your breath. Nothing resembling this is in our plans.
看起来他们推迟(忽略)这个bug并最终在同一年推出了Java 1.5,他们从0开始引入'enum',并决定从0开始(错过)不支持enum的null。后来在Java1.7(2011年7月)他们跟随(强制)使用String的相同哲学(即在生成字节码时,在调用hashcode()方法之前没有执行空检查)。
所以我认为它归结为enum首先出现的事实,并且它的序数从0开始实现,因为它们不能在switch块中支持null值,后来使用String它们决定强制使用相同的哲学,即null值不允许在开关块中。
TL; DR使用String,他们可以处理NPE(由尝试生成hash的hashcode引起),同时实现java代码到字节码转换,但最终决定不这样做。
根据Java Docs:
交换机使用byte,short,char和int原始数据类型。它还适用于枚举类型(在枚举类型中讨论),String类,以及一些包含某些基本类型的特殊类:Character,Byte,Short和Integer(在Numbers和Strings中讨论)。
由于null
没有类型,并且不是任何实例,因此它不适用于switch语句。
答案很简单,如果您使用带引用类型的开关(例如盒装基元类型),如果表达式为null,则会发生运行时错误,因为取消装箱会抛出NPE。
所以无论如何都无法执行null(这是非法的);)
我同意在@Paul Bellora的答案中https://stackoverflow.com/a/18263594/1053496的深刻见解(引擎盖下......)。
我从经验中找到了另一个原因。
如果'case'可以为null,则表示switch(变量)为null,那么只要开发人员提供匹配的'null'情况,我们就可以认为它很好。但是如果开发人员没有提供任何匹配的“null”情况会发生什么。然后我们必须将它与“默认”情况相匹配,这可能不是开发人员在默认情况下要处理的情况。因此,将“null”与默认值匹配可能会导致“令人惊讶的行为”。因此,抛出'NPE'将使开发人员明确处理每个案例。我发现在这种情况下投掷NPE非常周到。
使用Apache StringUtils类
String month = null;
switch (StringUtils.trimToEmpty(month)) {
case "xyz":
monthNumber=1;
break;
default:
monthNumber=0;
break;
}