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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/18263489/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-11 23:50:57  来源:igfitidea点击:

Why doesn't String switch statement support a null case?

javaswitch-statementlanguage-design

提问by Prashant Bhate

I am just wondering why the Java 7 switchstatement does not support a nullcase and instead throws NullPointerException? See the commented line below (example taken from the Java Tutorials article on switch):

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

This would have avoided an ifcondition for null check before every switchuse.

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

采纳答案by Paul Bellora

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

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

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

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

(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.

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

If we rather look at implementation details, this blog postby Christian Hujer has some insightful speculation about why nullisn't allowed in switches (although it centers on the enumswitch rather than the Stringswitch):

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

Under the hood, the switchstatement will typically compile to a tablesswitch byte code. And the "physical" argument to switchas 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 nullto 0wouldn'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.

switch幕后,该语句通常会编译为 tableswitch 字节代码。并且“物理”论据switch以及它的情况都是ints。要打开的 int 值是通过调用方法确定的Enum.ordinal()。[...] 序数从零开始。

这意味着,映射null0不是一个好主意。第一个枚举值上的开关与 null 没有区别。也许从 1 开始计算枚举的序数会是一个好主意。但是它没有被这样定义,而且这个定义不能改变。

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

虽然String开关的实现方式不同,但enum开关首先出现并开创了当引用为 时打开引用类型的行为方式的先例null

回答by amrith

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.

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

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

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

回答by BlackHatSamurai

According to Java Docs:

根据 Java 文档:

A switch works with the byte, short, char, and int primitive data types. It also works with enumerated types (discussed in Enum Types), the String class, and a few special classes that wrap certain primitive types: Character, Byte, Short, and Integer (discussed in Numbers and Strings).

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

Since nullhas no type, and is not an instance of anything, it will not work with a switch statement.

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

回答by Prashant Bhate

This is an attempt to answer why it throws NullPointerException

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

The output of the javap command below reveals that caseis chosen based on the hashcode of the switchargument string and hence throws NPE when .hashCode()is invoked on null string.

下面的 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
   }

This means, based on answers to Can Java's hashCode produce same value for different strings?, though rare, there is still a possibility of two cases being matched (two strings with same hash code) See this example below

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

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

As you can see only one case gets generated for "Ea"and "FB"but with two ifconditions to check for a match with each case string. Very interesting and complicated way of implementing this functionality!

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

回答by ZhongYu

In general nullis nasty to handle; maybe a better language can live without null.

一般来说null,处理起来很麻烦;也许没有null.

Your problem might be solved by

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

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

    }

回答by krispy

It isn't pretty, but String.valueOf()allows you to use a null String in a switch. If it finds null, it converts it to "null", otherwise it just returns the same String you passed it. If you don't handle "null"explicitly, then it will go to default. The only caveat is that there is no way of distinguishing between the String "null"and an actual nullvariable.

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

回答by sactiw

Long story short ... (and hopefully interesting enough!!!)

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

Enum were first introduced in Java1.5(Sep'2004) and the bugrequesting to allow switch on Stringwas filed long back (Oct'95). If you look at the comment posted on that bug at Jun'2004, it says Don't hold your breath. Nothing resembling this is in our plans.Looks like they deferred (ignored) this bug and eventually launched Java 1.5 in the same year in which they introduced 'enum' with ordinal starting at 0 and decided (missed) not to support null for enum. Later in Java1.7(Jul'2011) they followed (forced) the same philosophy with String (i.e. while generating the bytecode no null check was performed before calling hashcode() method).

枚举首先在介绍了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.72011 年 7 月)中,他们遵循了(强制) 与 String 相同的原理(即在生成字节码时,在调用 hashcode() 方法之前没有执行空检查)。

So I think it boils down to the fact enum came in first and was implemented with its ordinal begin at 0 due to which they couldn't support null value in switch block and later with String they decided to forced the same philosophy i.e. null value not allowed in switch block.

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

TL;DRWith String they could have take care of NPE (caused by attempt to generate hashcode for null) while implementing java code to byte code conversion but finally decided not to.

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

Ref: TheBUG, JavaVersionHistory, JavaCodeToByteCode, SO

参考: TheBUGJavaVersionHistoryJavaCodeToByteCodeSO

回答by nantitv

I agree with insightful comments (Under the hood ....) in https://stackoverflow.com/a/18263594/1053496in @Paul Bellora's answer.

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

I found one more reason from my experience.

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

If 'case' can be null that means switch(variable) is null then as long as the developer provides a matching 'null' case then we can argue it's fine . But what will happen if the developer does not provide any matching 'null' case. Then we have to match it to a 'default' case which may not be what developer has intended to handle in the default case. Therefore matching 'null' to a default could cause 'surprising behaviour'. Therefore throwing 'NPE' will make the developer to handle every cases explicitly. I found throwing NPE in this case very thoughtful.

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

回答by bhagat

Use Apache StringUtils class

使用 Apache StringUtils 类

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