Java切换 - 4个错误不等于1个正确

Java中的switch语句正在被改变。但是,这究竟是一种升级还是一种混乱?

经典的开关

Java中的经典switch语句并不是很好。 与Java的许多其他部分不同,它在多年前从C语言中提取功能时并没有进行适当的反思。

case关键的缺陷是 "fall-through-by-default"。这意味着,如果你忘记在每个break 子句,处理将继续到下一个case 子句。

另一个缺陷是变量的范围是整个开关,因此你不能在两个不同的case 子句中重复使用一个变量名。此外,default 子句不是必须的,这使得代码的读者不清楚是否有子句被遗忘。

当然,还有一个关键的限制--要切换的类型只能是整数、枚举或字符串。

 String instruction;
 switch (trafficLight) {
   case RED:
     instruction = "Stop";
   case YELLOW:
     instruction = "Prepare";
     break;
   case GREEN:
     instruction = "Go";
     break;
 }
 System.out.println(instruction);

复制代码

上面的代码不能编译,因为没有default 子句,导致instruction 未定义。但即使它编译了,由于缺少break ,它也不会打印 "Stop"。 在我自己的编码中,我喜欢总是把开关放在方法的末尾,每个子句都包含一个return ,以减少开关的风险。

升级后的switch

作为Project Amber的一部分,switch正在被升级。 但遗憾的是,我对新设计的优点不以为然。 说白了,有一些好的方面,但总的来说,我认为这个解决方案过于复杂,而且有一些令人不快的语法选择。

关键的目的是增加一个_表达式_,你可以把切换的结果分配给一个变量。这就像三元操作符(例如:x != null ? x : "" ),它相当于if语句的表达式。表达式将减少类似上述未定义变量的问题,因为它使每个分支必须产生一个变量的情况更加明显。

目前的计划是增加不是一种,而是三种新的开关形式。是的,三种。

在一篇博文中解释这一点,不出所料,要花点时间......

  • 类型1:具有经典语法的声明。和今天一样。有默认的下降。不是详尽的。
  • 类型2:具有经典语法的表达式。新!有默认的落差。必须是详尽的。
  • 类型3:具有新语法的语句。新!没有落差。非穷尽性。
  • 类型4:具有新语法的表达式。新!没有落差。必须是详尽的。

标题的例子(类型4)当然是相当不错的。

 // type 4
 var instruction = switch (trafficLight) {
   case RED -> "Stop";
   case YELLOW -> "Prepare";
   case GREEN -> "Go";
 };
 System.out.println(instruction);

复制代码

可以看到,类型3和类型4的新语法使用箭头而不是冒号。 而且,如果代码由单个表达式组成,就不需要使用break 。当使用枚举时,也不需要default 子句,因为只要你包含了所有已知的枚举值,编译器就可以为你插入它。所以,如果你漏掉了GREEN ,你会得到一个编译错误。

当然,魔鬼是在细节中的。

首先,一个明显的积极因素。与其因为列出多个标签而落空,不如用逗号来分隔它们。

 // type 4
 var instruction = switch (trafficLight) {
   case RED, YELLOW -> "Stop";
   case GREEN -> "Go";
 };
 System.out.println(instruction);

复制代码

直截了当,显而易见。并避免了许多简单的掉线用例。

如果要执行的代码比表达式更复杂怎么办?

 // type 4
 var instruction = switch (trafficLight) {
   case RED -> "Stop";
   case YELLOW -> {
     revYourEngine();
     yield "Prepare";
   }
   case GREEN -> "Go";
 };
 System.out.println(instruction);

复制代码

yield ?耸耸肩。在很长一段时间里,它将是break {expression} ,但这与标记的中断(一个很少使用的语法特征)相冲突。

那么类型2呢?

 // type 2
 var instruction = switch (trafficLight) {
   case RED: yield "Stop";
   case YELLOW:
     System.out.println("Prepare");
   case GREEN: yield "Go";
 };
 System.out.println(instruction);

复制代码

哎呀!我忘了yield 。所以,输入YELLOW会输出 "准备",然后落空,产生 "去"。

那么,为什么建议增加一种新的开关形式,重复20年前的错误呢? 答案是正交性--一个2x2的网格,表达式与语句,默认掉线与不掉线。

一个关键的问题是,正交性是否能证明在语言中添加一个几乎完全无用的开关形式(类型2)。

那么,类型3就可以了吗?

嗯,不是。由于坚持正交性,因此坚持复制与类型1语句切换有关的历史规则,没有要求列出所有case

 // type 3
 switch (trafficLight) {
   case RED -> doStop();
   case GO -> doGo();
 }

复制代码

那么,对于黄色会发生什么?答案是什么都没有,但是作为读者,我不禁要问,这段代码是正确的还是不完整的。如果上述情况是一个编译错误,开发人员被迫写一个default 子句,那就好得多。

 // type 3
 switch (trafficLight) {
   case RED -> doStop();
   case GO -> doGo();
   default -> {}
 }

复制代码

官方的说法是,既然1型语句切换(目前的)不能强制执行穷尽性,那么新的3型语句切换也不能。 我的看法是,保留20年前的糟糕设计是更大的罪过。

还有什么?嗯,需要记住的一点是,表达式不能提前完成,因此没有办法直接从switch表达式(类型2或4)中return 。也没有办法继续/中断一个循环。相信我,当我说在实际应用的规则中,有无穷无尽的Java考试问题。

总结一下这些类型

类型1:经典的语句切换

  • 语句
  • 通过默认的方式实现落地
  • return ,也允许继续/中断一个循环
  • 变量的单一范围
  • 每种情况下的逻辑是一连串的语句,可能以下列方式结束break
  • 并非详尽无遗--不需要默认条款
  • yield 是不允许的

类型2:经典语法表达式切换

  • 表达式
  • 通过默认的方式实现
  • return ,不允许,不能继续/中断一个循环
  • 变量的单一范围
  • 每种情况下的逻辑可以是一个产量表达式,或者是一个可能以下列内容结束的语句序列yield
  • 穷举--默认条款是必须的
  • 必须使用yield 来返回值

类型3:箭头形式的语句切换

  • 语句
  • 不允许落空
  • return ,也允许继续/中断一个循环
  • 没有变量范围问题,每种情况的逻辑必须是一个语句或一个块
  • 不是详尽的--不需要默认条款
  • yield 是不允许的

类型4:箭头形式的表达式切换

  • 表达式
  • 不允许回避
  • return 不允许,不能继续/中断循环
  • 没有变量范围问题,每种情况下的逻辑必须是一个表达式或一个块,结尾是yield
  • 穷举--默认条款是必须的
  • 必须使用yield 来返回值,但只能从块中返回(当不是块时,它是隐含的)。

你困惑了吗?

好吧,我肯定我没有完美地解释一切,而且我很可能在某个地方犯了一个错误。但现实是,它复杂,有很多规则隐藏在众目睽睽之下。 是的,它是正交的。但我真的不认为这有助于理解这个特征。

我会怎么做呢?

第4类switch表达式很好(尽管我对从lambdas延伸出来的箭头语法确实有意见)。 我的问题是第2类和第3类。在现实中,这两类switch ,会非常罕见,因此大多数开发者都不会看到它们。鉴于此,我认为最好是完全不包括它们。一旦接受这一点,将表达式形式作为switch ,就没有意义了,因为它实际上不会与旧的语句形式有很多联系。

我将放弃类型2和3,并允许类型4的开关表达式成为所谓的_语句表达式_。(语句表达式的另一个例子是方法调用,它可以作为表达式或作为自己一行的语句使用,忽略任何返回值)。

 // Stephen's expression switch
 var instruction = match (trafficLight) {
   case RED: "Stop";
   case YELLOW: "Prepare";
   case GO: "Go";
 }
 // Stephen's expression switch used as a statement (technically a statement expression)
 match (instruction) {
   case "Stop": doStop();
   case "Go": doGo();
   default: ;
 }

复制代码

我的方法使用了一个新的关键字match ,因为我认为扩展switch 是错误的使用基线。把它变成一个语句表达式意味着只有一套规则--它总是一个表达式,只是你可以把它当作一个语句来使用。用我的方法做不到的是在语句版本中使用return ,因为它实际上不是一个语句(今天你不能从 Java 的任何表达式中使用return ,所以这也没什么不同)。

总结

如果你忽略了复杂性,而只是使用4型开关表达式,那么这个新特性是相当合理的。

然而,为了增加Java所需要的一种开关形式,我们还得到了另外两个哑巴--2型和3型。 在我看来,这个功能需要回到绘图板,但遗憾的是,我怀疑现在已经太晚了。

猜你喜欢

转载自juejin.im/post/7055602261565079560
今日推荐