JEP 325:切换表达式(预览)
概括
扩展该switch
语句,以便它可以用作语句或表达式,并且两种形式都可以使用“传统”或“简化”范围和控制流行为。这些更改将简化日常编码,并为在switch
.这是JDK 12 中的预览语言功能。
请注意:此 JEP 已被JEP 354取代,后者针对 JDK 13。
动机
当我们准备增强 Java 编程语言以支持模式匹配 (JEP 305)时,现有switch
语句的一些不规则之处(长期以来一直令用户烦恼)成为了障碍。这些包括 switch 块的默认控制流行为(失败)、switch 块的默认作用域(该块被视为一个单一作用域)以及switch
仅作为一条语句工作,尽管通常更自然地表达多个方式条件作为表达式。
目前Java语句的设计switch
紧密遵循C、C++等语言,并且默认支持fall-through语义。虽然这种传统的控制流对于编写低级代码(例如二进制编码的解析器)通常很有用,但由于 switch 用于高级上下文,但其容易出错的性质开始超过其灵活性。
例如,在下面的代码中,许多break
语句使其变得不必要的冗长,并且这种视觉噪音通常掩盖了难以调试的错误,其中缺少break
语句意味着会发生意外失败。
switch (day) {
case MONDAY:
case FRIDAY:
case SUNDAY:
System.out.println(6);
break;
case TUESDAY:
System.out.println(7);
break;
case THURSDAY:
case SATURDAY:
System.out.println(8);
break;
case WEDNESDAY:
System.out.println(9);
break;
}
我们建议引入一种新形式的 switch 标签,写为“ case L ->
”,表示如果标签匹配,则只执行标签右侧的代码。例如,之前的代码现在可以写成:
switch (day) {
case MONDAY, FRIDAY, SUNDAY -> System.out.println(6);
case TUESDAY -> System.out.println(7);
case THURSDAY, SATURDAY -> System.out.println(8);
case WEDNESDAY -> System.out.println(9);
}
(此示例还使用多个 case 标签:我们建议在单个 switch 标签中支持多个逗号分隔的标签。)
“ ”开关标签右侧的代码case L ->
仅限于表达式、块或(为了方便起见)throw
语句。这具有令人愉快的结果,如果一个臂引入局部变量,它必须包含在一个块中,因此不在开关块中任何其他臂的范围内。这消除了“传统”开关块的另一个烦恼,其中局部变量的范围是整个开关块。
switch (day) {
case MONDAY:
case TUESDAY:
int temp = ...
break;
case WEDNESDAY:
case THURSDAY:
int temp2 = ... // Why can't I call this temp?
break;
default:
int temp3 = ... // Why can't I call this temp?
}
许多现有switch
语句本质上是表达式的模拟switch
,其中每个分支要么分配给一个公共目标变量,要么返回一个值:
int numLetters;
switch (day) {
case MONDAY:
case FRIDAY:
case SUNDAY:
numLetters = 6;
break;
case TUESDAY:
numLetters = 7;
break;
case THURSDAY:
case SATURDAY:
numLetters = 8;
break;
case WEDNESDAY:
numLetters = 9;
break;
default:
throw new IllegalStateException("Wat: " + day);
}
将其表达为陈述是迂回、重复且容易出错的。作者的意思是我们应该计算numLetters
每一天的值。应该可以直接用switch
_表达式_来表示,这样更清晰、更安全:
int numLetters = switch (day) {
case MONDAY, FRIDAY, SUNDAY -> 6;
case TUESDAY -> 7;
case THURSDAY, SATURDAY -> 8;
case WEDNESDAY -> 9;
};
反过来,扩展switch
到支持表达式会引发一些额外的需求,例如扩展流分析(表达式必须始终计算值或突然完成),并允许表达式的某些 case 分支switch
抛出异常而不是产生值。
描述
除了“传统”开关块之外,我们建议添加一个新的“简化”形式,并带有新的“ case L ->
”开关标签。如果标签匹配,则仅执行箭头标签右侧的表达式或语句;没有跌倒。例如,给定方法:
static void howMany(int k) {
switch (k) {
case 1 -> System.out.println("one");
case 2 -> System.out.println("two");
case 3 -> System.out.println("many");
}
}
下面的代码:
howMany(1);
howMany(2);
howMany(3);
产生以下输 出:
one
two
many
我们将扩展该switch
语句,以便它还可以用作表达式。在常见情况下,switch
表达式将如下所示:
T result = switch (arg) {
case L1 -> e1;
case L2 -> e2;
default -> e3;
};
表达式switch
是一个多表达式;如果目标类型已知,则将该类型向下推送到每个臂中。表达式的类型switch
是其目标类型(如果已知);如果不是,则通过组合每个案例臂的类型来计算独立类型。
大多数switch
表达式在“”开关标签右侧都有一个表达式case L ->
。如果需要完整的块,我们扩展了该break
语句以接受一个参数,该参数将成为封闭表达式的值switch
。
int j = switch (day) {
case MONDAY -> 0;
case TUESDAY -> 1;
default -> {
int k = day.toString().length();
int result = f(k);
break result;
}
};
switch
与语句一样,表达式也可以使用带有“ ”开关标签(暗示失败语义)的switch
“传统”开关块。case L:
在这种情况下,将使用 with value 语句生成值break
:
int result = switch (s) {
case "Foo":
break 1;
case "Bar":
break 2;
default:
System.out.println("Neither Foo nor Bar, hmmm...");
break 0;
};
(with and without value)的两种形式break
类似于return
in 方法的两种形式。两种形式都return
立即终止方法的执行;在非 void 方法中,还必须提供一个值,该值将产生给方法的调用者。 (break
表达式值和标签形式之间的歧义break
可以相对容易地处理。)
表达式的情况switch
必须是详尽无遗的;对于任何可能的值都必须有一个匹配的开关标签。在实践中,这通常仅仅意味着default
需要一个条款;但是,在enum
switch
表达式涵盖所有已知情况(最终是switch
密封类型上的表达式)的情况下,default
编译器可以插入一个子句,指示enum
定义在编译时和运行时之间发生了更改。 (这是开发人员今天手工完成的工作,但是让编译器插入它比手工编写的侵入性更小,并且可能会产生更具描述性的错误消息。)
此外,switch
表达式必须正常完成并带有值或throw
异常。这会产生许多后果。首先,编译器检查每个开关标签,如果匹配,则可以生成一个值。
int i = switch (day) {
case MONDAY -> {
System.out.println("Monday");
// ERROR! Block doesn't contain a break with value
}
default -> 1;
};
i = switch (day) {
case MONDAY, TUESDAY, WEDNESDAY:
break 0;
default:
System.out.println("Second half of the week");
// ERROR! Group doesn't contain a break with value
};
进一步的结果是控制语句 、break
和return
不能continue
跳过switch
表达式,如下所示:
z:
for (int i = 0; i < MAX_VALUE; ++i) {
int k = switch (e) {
case 0:
break 1;
case 1:
break 2;
default:
continue z;
// ERROR! Illegal jump through a switch expression
};
...
}
作为一个机会目标,我们可以扩展 switch 以支持以前不允许的基本类型(及其框类型)的切换,例如 float、double 和 long。