137

我只是想知道为什么 Java 7switch语句不支持nullcase 而是 throws 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

4

9 回答 9

152

As damryfbfnetsi points out in the comments, JLS §14.11 has the following note:

The prohibition against using null as a switch label prevents one from writing code that can never be executed. If the switch expression is of a reference type, that is, String or a boxed primitive type or an enum type, then a run-time error will occur if the expression evaluates to null at run time. In the judgment of the designers of the Java programming language, this is a better outcome than silently skipping the entire switch statement or choosing to execute the statements (if any) after the default label (if any).

(emphasis mine)

While the last sentence skips over the possibility of using case null:, it seems reasonable and offers a view into the language designers' intentions.

If we rather look at implementation details, this blog post by Christian Hujer has some insightful speculation about why null isn't allowed in switches (although it centers on the enum switch rather than the String switch):

Under the hood, the switch statement will typically compile to a tablesswitch byte code. And the "physical" argument to switch as well as its cases are ints. The int value to switch on is determined by invoking the method Enum.ordinal(). The [...] ordinals start at zero.

That means, mapping null to 0 wouldn't be a good idea. A switch on the first enum value would be indistinguishible from null. Maybe it would've been a good idea to start counting the ordinals for enums at 1. However it hasn't been defined like that, and this definition can not be changed.

While String switches are implemented differently, the enum switch came first and set the precedent for how switching on a reference type should behave when the reference is null.

于 2013-08-15T23:30:54.833 回答
38

一般来说null很难处理;也许没有null.

您的问题可能会通过以下方式解决

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

    }
于 2013-08-16T02:07:17.037 回答
29

它不漂亮,但String.valueOf()允许您在开关中使用空字符串。如果找到null,则将其转换为"null",否则仅返回您传递给它的相同字符串。如果您不"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;
于 2015-04-26T02:45:00.117 回答
16

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

下面 javap 命令的输出显示它case是根据参数字符串的哈希码选择的,因此在对空字符串调用 switch时会抛出 NPE 。.hashCode()

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

正如您所看到的,只生成了一个 case "Ea""FB"但是有两个if条件来检查每个 case 字符串是否匹配。实现此功能的非常有趣和复杂的方式!

于 2013-08-16T00:22:56.830 回答
8

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

Enum 最早是在Java1.52004年 9 月)中引入的,并且请求允许打开 String的bug早就提交了(95年 10 月)。如果您查看 2004 年 6 月发布的关于该错误的评论它说看起来他们推迟(忽略)了这个错误,并最终在同一年推出了 Java 1.5,在同一年他们引入了序号从 0 开始的“枚举”并决定(错过) 不支持枚举的 null。后来在Java1.72011年 7 月)他们跟随(强制Don't hold your breath. Nothing resembling this is in our plans.) 与 String 相同的理念(即,在生成字节码时,在调用 hashcode() 方法之前没有执行空值检查)。

所以我认为这归结为枚举首先出现并以它的序数从 0 开始实现的事实,因此他们不能在 switch 块中支持空值,后来他们决定使用 String 他们决定强制使用相同的理念,即空值不在 switch 块中允许。

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

参考: TheBUG , JavaVersionHistory , JavaCodeToByteCode , SO

于 2015-06-30T17:00:35.177 回答
1

根据 Java 文档:

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

由于null没有类型,也不是任何事物的实例,因此它不适用于 switch 语句。

于 2013-08-15T23:35:42.583 回答
0

The answer is simply that if you use a switch with a reference type (such as a boxed primitive type), the run-time error will occur if the expression is null because unboxing it would throw the NPE.

so case null (which is illegal) could never be executed anyway ;)

于 2013-08-15T23:33:17.533 回答
0

我同意@Paul Bellora 的回答中https://stackoverflow.com/a/18263594/1053496中富有洞察力的评论(幕后......)。

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

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

于 2017-07-27T08:22:05.550 回答
0

使用 Apache StringUtils 类

String month = null;
switch (StringUtils.trimToEmpty(month)) {
    case "xyz":
        monthNumber=1;  
    break;
    default:
       monthNumber=0;
    break;
}
于 2019-04-04T07:31:28.240 回答