Java 为什么';t字符串开关语句是否支持空值?

Java 为什么';t字符串开关语句是否支持空值?,java,switch-statement,language-design,Java,Switch Statement,Language Design,我只是想知道为什么Java7switch语句不支持null大小写,而是抛出NullPointerException?请参见下面的注释行(示例取自): 这将避免在每次使用开关之前出现if空检查的情况。如注释中的damryfbfnetsi,有以下注释: 禁止将null用作开关标签可防止编写无法执行的代码。如果开关表达式属于引用类型,即字符串或装箱基元类型或枚举类型,则如果表达式在运行时的计算结果为null,则会发生运行时错误根据Java编程语言的设计者的判断,这比无声地跳过整个开关语句或选择在默认标

我只是想知道为什么Java7
switch
语句不支持
null
大小写,而是抛出
NullPointerException
?请参见下面的注释行(示例取自):

这将避免在每次使用
开关之前出现
if
空检查的情况。

如注释中的damryfbfnetsi,有以下注释:

禁止将
null
用作开关标签可防止编写无法执行的代码。如果
开关
表达式属于引用类型,即
字符串
或装箱基元类型或枚举类型,则如果表达式在运行时的计算结果为
null
,则会发生运行时错误根据Java编程语言的设计者的判断,这比无声地跳过整个
开关
语句或选择在
默认
标签(如果有的话)后执行语句(如果有的话)要好得多。

(强调矿山)

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

如果我们更愿意看一下实现细节,Christian Hujer对为什么开关中不允许使用
null
(尽管它集中在
enum
开关上,而不是
String
开关上)有一些深刻的推测:

在引擎盖下,
switch
语句通常会编译为tableswitch字节码。
开关的“物理”参数及其大小写是
int
s。要打开的int值是通过调用方法
Enum.ordinal()
确定的。[…]序数从零开始

这意味着,将
null
映射到
0
不是一个好主意。第一个枚举值上的开关与null不可区分。也许从1开始计算枚举的序数是个好主意。但是它并没有这样定义,这个定义是不能改变的


String
开关切换时,
enum
开关首先出现,并为在引用为
null

时切换引用类型的行为开创了先例。答案很简单,如果您将开关与引用类型一起使用(例如装箱的基元类型),如果表达式为null,则会发生运行时错误,因为取消装箱会引发NPE


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

根据Java文档:

一个开关可以处理byte、short、char和int原语数据 类型。它也适用于枚举类型(在枚举类型中讨论), String类,以及一些包装特定 基本类型:字符、字节、短字符和整数(在中讨论 数字和字符串)

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

这是一个尝试,旨在回答它抛出
NullPointerException
下面javap命令的输出显示,
case
是根据
开关
参数字符串的hashcode选择的,因此在对空字符串调用
.hashcode()
时抛出NPE

6: invokevirtual #18                 // Method java/lang/String.hashCode:()I
9: lookupswitch  { // 3
    -1826660246: 44
     -263893086: 56
      103666243: 68
        default: 95
   }
这意味着,根据对的回答,尽管很少,但仍有可能匹配两个案例(两个字符串具有相同的哈希代码),请参见下面的示例

    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”
生成了一个case,但有两个
if
条件来检查每个case字符串是否匹配。实现此功能的方式非常有趣且复杂

通常
null
很难处理;也许更好的语言可以在没有
null
的情况下生存

你的问题可以通过以下方法解决

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

    }

它并不漂亮,但是
String.valueOf()
允许您在开关中使用空字符串。如果它发现
null
,它会将其转换为
“null”
,否则它只返回与传递它的字符串相同的字符串。如果您没有显式地处理
“null”
,那么它将转到
默认值。唯一需要注意的是,无法区分字符串
“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;
长话短说。。。(希望足够有趣!!!)

Enum最早于(2004年9月)引入,请求允许打开字符串的文件很早就提交了(1995年10月)。如果你在2004年6月的上看到关于这个bug的评论,它说
不要屏住呼吸。我们的计划中没有类似的内容。
看起来他们推迟(忽略)了这个错误,并最终在同一年推出了Java 1.5,在这一年中,他们引入了序号为0的“enum”,并决定(错过)不支持null for enum。后来(2011年7月)他们对字符串遵循(强制)相同的原理(即,在生成字节码时,在调用hashcode()方法之前没有执行空检查)

所以我认为这可以归结为这样一个事实:enum是首先出现的,它的序号开始于0,因此它们不能在开关块中支持空值,后来它们决定在字符串中强制使用相同的原理,即开关块中不允许空值

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

参考: , , ,

我同意@Paul Bellora的回答中有深刻见解的评论(在引擎盖下…)

我从我的经历中又找到了一个原因

如果“case”可以为null,则表示开关(variabl
    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;
String month = null;
switch (StringUtils.trimToEmpty(month)) {
    case "xyz":
        monthNumber=1;  
    break;
    default:
       monthNumber=0;
    break;
}