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

时间:2013-08-15 23:20:05

标签: java switch-statement language-design

我只是想知道为什么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条件进行空检查。

9 个答案:

答案 0 :(得分:135)

作为评论中的damryfbfnetsi points outJLS §14.11有以下注释:

  

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

(强调我的)

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

如果我们宁愿查看实施细节,那么Christian Hujer的this blog post就交换机中不允许null的原因有一些深刻的猜测(尽管它以enum开关为中心而非String开关):

  

引擎盖下,switch语句通常会编译为tablesswitch字节代码。而switch及其案例的“物理”论证是int。要打开的int值是通过调用方法Enum.ordinal()来确定的。 [...]序数从零开始。

     

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

String切换are implemented differently时,enum切换位于第一位,并为引用为null时如何切换引用类型设置先例。

答案 1 :(得分:27)

一般来说,null很难处理;也许更好的语言可以在没有null的情况下生活。

您的问题可以通过

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

    }

答案 2 :(得分:23)

它不漂亮,但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;

答案 3 :(得分:14)

这是尝试回答它抛出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条件来检查每个案例字符串的mach。 实现此功能的非常有趣和复杂的方式!

答案 4 :(得分:4)

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

Enum最初是在Java1.5 Sep'2004')中引入的,并且bug请求允许切换字符串已经提交了很长时间(的 1995年10月)。如果您查看2004年6月 2004年>上该错误发布的评论,它会显示Don't hold your breath. Nothing resembling this is in our plans.看起来他们推迟了(忽略)此错误并最终启动了Java 1.5在同一年,他们引入了序号从0开始的'enum'并决定(错过)不支持enum的null。后来在Java1.7 2011年7月)中,他们跟随(强制)使用String的相同哲学(即生成字节码时,在调用哈希码之前未执行空检查()方法)。

所以我认为它归结为enum首先出现的事实,并且它的序数从0开始实现,因为它们不能在switch块中支持null值,后来使用String它们决定强制使用相同的哲学,即切换块中不允许使用空值。

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

价: TheBUGJavaVersionHistoryJavaCodeToByteCodeSO

答案 5 :(得分:1)

根据Java Docs:

  

开关使用byte,short,char和int原始数据   类型。它也适用于枚举类型(在枚举类型中讨论),   String类,以及一些特定的包装类   原始类型:字符,字节,短整数和整数(在   数字和字符串)。

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

答案 6 :(得分:0)

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

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

答案 7 :(得分:0)

我同意@Paul Bellora的答案中https://stackoverflow.com/a/18263594/1053496中的深刻见解(引擎盖......)。

我从经验中找到了另一个原因。

如果'case'可以为null,表示switch(变量)为null,那么只要开发人员提供匹配的'null'情况,我们就可以认为它很好。但是如果开发人员没有提供任何匹配的“null”情况会发生什么。然后我们必须将它与“默认”情况相匹配,这可能不是开发人员在默认情况下要处理的情况。因此,将“null”与默认值匹配可能会导致“令人惊讶的行为”。 因此,抛出'NPE'将使开发人员明确处理每个案例。我觉得在这种情况下投入NPE非常周到。

答案 8 :(得分:0)

使用Apache StringUtils类

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