为什么String switch语句不支持null大小写?


125

我只是想知道为什么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;
}

这样可以避免if在每次switch使用之前都进行空检查的条件。


12
由于我们不是创造该语言的人,因此没有最终的答案。所有答案都是纯推测。
asteri

2
尝试打开null将导致异常。对进行if检查null,然后进入switch语句。
gparyani

28
来自JLS根据Java编程语言的设计人员的判断,[ NullPointerException在表达式null运行时对表达式求值时抛出a ]比静默跳过整个switch语句或选择在执行之后执行语句(如果有)要好。默认标签(如果有)。
gparyani

3
@gparyani:回答一下。这听起来非常正式和明确。
Thilo

7
@JeffGohlke:“除非您是做出决定的人,否则无法回答为什么要提出的问题。” ...好吧,gparyani的评论证明不是这样
user541686

Answers:


145

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

禁止将其null用作开关标签,以防止他人编写永远无法执行的代码。如果switch表达式是引用类型,即String装箱的原始类型或枚举类型,则如果表达式null在运行时求值为,则会发生运行时错误。根据Java编程语言的设计人员的判断,与静默跳过整个switch语句或选择在default标签(如果有)之后执行语句(如果有)相比,这是更好的结果。

(强调我的)

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

如果我们宁愿看一下实现细节,Christian Hujer的这篇博客文章就为什么null不允许在交换机中进行了一些有见地的推测(尽管它集中在enum交换机而不是String交换机上):

switch幕后,该语句通常将编译为一个tableswitch字节码。s 的“物理”论证switch及其案例int。通过调用该方法确定要打开的int值Enum.ordinal()。序号从零开始。

这意味着映射null0不是一个好主意。第一个枚举值的开关将与null不可区分。从1开始计数枚举的序数也许是个好主意。但是,还没有像这样定义它,因此不能更改此定义。

String开关的实现方式不同时enum开关优先,并为引用类型为引用类型时的行为开创了先例null


1
如果将对null case null:进行专有处理,那么将null处理作为a的一部分将是一个很大的改进String。目前,所有String检查都需要进行空检查,如果我们想对其进行纠正,尽管大多数情况下都是隐式地将字符串常量放在象中那样"123test".equals(value)。现在我们被迫在if (value != null) switch (value) {...
YoYo '02

1
关于“将null映射为0并不是一个好主意”:这是轻描淡写的,因为“” .hashcode()的值为0!这意味着必须在switch语句中对空字符串和零长度字符串进行相同处理,这显然是不可行的。
skomisa

对于枚举,阻止它们将null映射到-1的原因是什么?
krispy19年

31

一般来说null,处理起来很讨厌;也许没有一种语言会更好null

您的问题可能通过解决

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

    }

month一个空字符串不是一个好主意:这将与空字符串相同。
gparyani

13
在许多情况下,将null视为空字符串可能是完全合理的
Eric Woodruff

23

它不是很漂亮,但是String.valueOf()允许您在开关中使用null字符串。如果找到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;

2
我相信在Java中做这样的事情是一种反模式。
卢卡斯Rzeszotarski

2
@ŁukaszRzeszotarski这几乎就是我所说的“不漂亮”的意思
krispy

15

这是试图回答为什么会抛出的尝试 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
   }

这意味着,基于对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);

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

如您所见,仅针对"Ea"和生成一个案例,"FB"但是有两个if条件来检查每个案例字符串是否匹配。实现此功能的非常有趣且复杂的方式!


5
但是,可以用另一种方式来实现。
Thilo

2
我会说这是一个设计错误。
迪尔·亨特

6
我想知道这是否与为什么字符串哈希函数的某些可疑方面没有改变有关:通常,代码不应依赖于hashCode在程序的不同运行中返回相同的值,但是由于字符串哈希被烘焙到可执行文件中在编译器中,字符串哈希方法最终成为语言规范的一部分。
2013年

5

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

枚举首先在介绍了Java1.5的Sep'2004)和错误请求允许在字符串开关被提起长回(1995年10月)。如果你看一下张贴在该bug的评论Jun'2004,它说,Don't hold your breath. Nothing resembling this is in our plans.看起来他们推迟(忽略)这个bug,并最终推出的Java 1.5中,他们推出了“枚举”与序同年从0开始,并决定(错过)不支持枚举为null。后来在Java1.7Jul'2011)他们跟(强制)与String相同的原理(即,在生成字节码时,在调用hashcode()方法之前未执行空检查)。

因此,我认为它归结为以下事实:枚举首先出现,并以序数开头为0的方式实现,这是因为它们无法在切换块中支持空值,后来又使用String决定强制采用相同的原理,即不使用空值在开关块中允许。

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

参考: TheBUGJavaVersionHistoryJavaCodeToByteCodeSO


1

根据Java Docs:

开关适用于byte,short,char和int基本数据类型。它也适用于枚举类型(在Enum Types中讨论),String类以及一些包装某些基本类型的特殊类:Character,Byte,Short和Integer(在Numbers和Strings中讨论)。

由于null没有类型,并且不是任何实例,因此它不能与switch语句一起使用。


4
然而null是一个有效的值StringCharacterByteShort,或Integer参考。
asteri

0

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

因此,无论如何都不能执行null(非法)的情况;)


1
但是,可以用另一种方式来实现。
Thilo

OK @Thilo,比我聪明的人参与了此实现。如果您知道可以实施此方法的其他方式,我很想知道这些是什么[而且我肯定还有其他],所以
也要

3
字符串不是装箱的原始类型,并且不会发生NPE,因为有人试图对其进行“拆箱”。
Thilo

@thilo,实现此目标的其他方法是什么?
2013年

3
if (x == null) { // the case: null part }
Thilo

0

我同意@Paul Bellora的回答中https://stackoverflow.com/a/18263594/1053496中有见地的评论(内幕..)。

根据我的经验,我发现了另一个原因。

如果'case'可以为null,则表示switch(variable)为null,那么只要开发人员提供匹配的'null'案例,我们就可以认为这很好。但是,如果开发人员不提供任何匹配的“空”情况,将会发生什么。然后,我们必须将其匹配到“默认”情况,这可能不是开发人员打算在默认情况下处理的情况。因此,将“ null”与默认值匹配可能会导致“令人惊讶的行为”。因此,抛出“ NPE”将使开发人员能够明确处理所有情况。我发现在这种情况下扔NPE非常周到。


0

使用Apache StringUtils类

String month = null;
switch (StringUtils.trimToEmpty(month)) {
    case "xyz":
        monthNumber=1;  
    break;
    default:
       monthNumber=0;
    break;
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.