一、概要

扩展 switch,使其可以作为语句或表达式使用,以及使两种形式都可以使用传统的 case … : 标签(具有穿透效果)或新的 case … -> 标签(没有穿透效果),并添加一个新的语句用于从 switch 表达式中产生值。这些更改将简化日常编码,并为在 switch 中使用模式匹配铺平道路。这是 JDK 12 和 JDK 13 中的一个预览语言特性。

扩展switch,使其既可以用作语句,也可以用作语句,并且这两种形式都可以使用传统的case ...:标签,或新的case ...->标签。并添加了一个新的语句用于从switch表达式中生成值。这些更改将简化日常编码,并为在switch中使用模式匹配做好准备。这是JDK 12和JDK 13中的一个预览语言特性。

二、历史

JEP 325在2017年12月提出了switch表达式。JEP 325在2018年8月作为JDK 12的预览功能。其中的一个特性是重载break语句以从switch表达式获取返回值。但是在JDK 12的反馈表明这种break的使用是令人困惑的。所以,作为对JEP 325反馈的回应,JEP 354作为JEP 325的演变被创建。

JEP 354提出了一种新的语句yield,并且恢复了break原本的含义。JEP 354在2019年6月作为JDK 13的预览功能。对JDK 13的反馈表明,switch已经不用再进行任何更改,可以作为JDK 14中的最终和永久版本的表达式。

三、动机

当JDK的开发人员准备增强Java编程语言以支持模式匹配(JEP 305: Pattern Matching for instanceof (Preview) (openjdk.org))时,现有的switch语句的一些长期困扰用户的的不规则性成为了阻碍。其中包括开关标签之间的默认控制流行为(case语句贯穿)、default块,以及switch仅仅作为一个语句的事实。

虽然这种传统的控制流通常适用于便捷低级代码,但是由于switch在高层级上下文中的使用,其容易出错的特性开始超过其灵活性。

例如,在下面的代码中,许多语句使其变得冗长,而这种视觉上的繁杂经常会掩盖难以调试的错误,其中丢失的语句意味着错误的发生。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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;
}

引入了一种新形式的标签,如果标签匹配,则仅执行标签右侧的代码。上面的代码可以改成这样:

1
2
3
4
5
6
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);
}

->标签右侧的代码被限制为表达式、块或语句。这有一个令人愉悦的效果:如果一个分支的代码块中声明了一个局部变量,它只能在当前分支块中被使用,不能在其他分支中使用。这消除了传统switch的另一个烦恼,即局部变量的范围是整个开关块。示例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
switch (day) {  
case MONDAY:
case FRIDAY:
case SUNDAY:
int temp = 0;
System.out.println(6);
System.out.println("temp " + temp);
case TUESDAY:
temp = 2;
System.out.println(7);
System.out.println("temp " + temp);
break;
}
1
2
3
4
5
6
7
8
9
10
switch (day) {  
case MONDAY, FRIDAY, SUNDAY -> {
int temp = 0;
System.out.println(6);
}
case TUESDAY -> {
// 不能引用 temp 变量
System.out.println(7);
}
}

许多现有的语句,其中每个分支要么分配给一个共同的目标变量,要么返回一个值,就像下面这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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);
}
return numLetters;

上面的代码可以这样代替:

1
2
3
4
5
6
7
int numLetters = switch (day) {  
case MONDAY, FRIDAY, SUNDAY -> 6;
case TUESDAY -> 7;
case THURSDAY, SATURDAY -> 8;
case WEDNESDAY -> 9;
};
return numLetters;

四、详情

1、箭头标签

1
2
3
4
5
6
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);
}

2、Switch 表达式

1
2
3
4
5
6
int numLetters = switch (day) {  
case MONDAY, FRIDAY, SUNDAY -> 6;
case TUESDAY -> 7;
case THURSDAY, SATURDAY -> 8;
case WEDNESDAY -> 9;
};

3、yield关键字

大多数开关表达式都会在->右侧有一个表达式。因为有时候需要一个完整的块,所以引入了一个新的yield语句来产生一个值。

1
2
3
4
5
6
7
8
int j = switch (day) {  
case MONDAY -> 0;
case TUESDAY -> 1;
default -> {
String enumString = day.toString();
yield enumString.length();
}
};

像switch语句一样,switch表达式也可以使用带有case标签的传统switch块。

1
2
3
4
5
6
7
8
9
10
int result = switch (string) {
case "Foo":
yield 1;
case "Bar":
yield 2;
default:
System.out.println("no Foo, no Bar");
yield 0;
};
return result;

break和yield这两个语句有助于轻松消除switch语句和switch表达式之间的歧义。

yield不是一个关键字,而是一个受限制的标识符(比如var),这意味着命名为yield的类是不合法的。

4、扩展

在switch表达式(2、Switch 表达式)中,所涉及到的分支场景必须要是全面的,即所有的情况必须要涉及一个case标签。然而switch语句不需要覆盖所有的情况。

这意味着,一个default语句块是必须的。在一个覆盖所有情况的枚举switch表达式中,编译器会插入一个默认子句,用来指示枚举定义在编译时和运行时之间发生了更改。隐含地插入默认子句,使得代码更加健壮。当编译器编译代码时,编译期会检查是否显示处理了所有情况,如果开发人员插入了一个显示的默认子句,这可能会隐含一个错误。

此外,控制语句break, yield, return, continue不能跳过开关表达式,如下:

1
2
3
4
5
6
7
8
9
for (int i = 0; i < 10; i++) {  
int k = switch (day) {
case MONDAY -> 1;
case FRIDAY -> 2;
default:
// error
continue;
};
}

相关链接

JEP 361: Switch Expressions (openjdk.org)

JEP 305: Pattern Matching for instanceof (Preview) (openjdk.org)

OB tags

#JDK新特性