为什么String switch语句不支持null case?

Pra*_*ate 115 java language-design switch-statement

我只是想知道为什么Java 7 switch语句不支持一个null案例而是抛出NullPointerException?请参阅下面的注释行(示例摘自Java Tutorials文章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;
}
Run Code Online (Sandbox Code Playgroud)

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

Pau*_*ora 138

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

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

(强调我的)

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

如果我们宁愿查看实现细节,Christian Hujer撰写的这篇博文有一些深刻的猜测,说明为什么null交换机不允许这样做(尽管它以交换机为中心enum而不是String交换机):

在引擎盖下,switch语句通常会编译为tablesswitch字节代码.并且switch它的案例的"物理"论证是ints.要打开的int值是通过调用方法确定的Enum.ordinal().[...]序数从零开始.

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

虽然String交换机的实现方式不同,但enum交换机首先出现,并为引用类型在引用类型时应如何进行操作设置了先例null.

  • 如果它是专门为“String”实现的,那么允许空处理作为“case null:”的一部分将是一个很大的改进。目前,如果我们想正确执行所有“String”检查,无论如何都需要进行空检查,尽管大多数情况下都是隐式地将字符串常量放在前面,如“123test”.equals(value)`中那样。现在我们被迫将 switch 语句写成 `if (value != null) switch (value) {...` (3认同)
  • 对于枚举的情况,是什么阻止他们将 null 映射到 -1? (2认同)

Zho*_*gYu 30

总的来说null是讨厌的处理; 也许更好的语言可以没有null.

您的问题可能会被解决

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

    }
Run Code Online (Sandbox Code Playgroud)

  • 对于许多情况,将null视为空字符串可能是完全合理的 (12认同)

kri*_*spy 23

它不漂亮,但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;
Run Code Online (Sandbox Code Playgroud)

  • 我相信在java中做这样的事情是反模式. (2认同)
  • @ŁukaszRzeszotarski 这就是我所说的“它不漂亮”的意思 (2认同)

Pra*_*ate 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
   }
Run Code Online (Sandbox Code Playgroud)

这意味着基于Can 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);
Run Code Online (Sandbox Code Playgroud)

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
Run Code Online (Sandbox Code Playgroud)

好吧,你可以看到只有一个案例被生成但有两个if条件来检查每个案例字符串的mach.实现此功能非常有趣和复杂的方式!

  • 我想知道这是否与为什么字符串哈希函数的某些可疑方面没有改变有关:一般来说,代码不应该依赖 `hashCode` 在程序的不同运行中返回相同的值,但是因为字符串哈希被烘焙编译器进入可执行文件后,字符串哈希方法最终成为语言规范的一部分。 (6认同)
  • 不过,这可能是以另一种方式实施的. (5认同)
  • 我会说这是一个设计错误. (2认同)

sac*_*tiw 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