本文是对官方文档(https://openjdk.org/jeps/406)的概括总结,更详细的内容请看官方文档的不完全翻译(https://blog.csdn.net/weixin_38833041/article/details/125450755)
文章目录
简介
在Java 17 preview中,switch支持模式匹配,并且允许选择器表达式的值为null。模式分为两种,分别是保护模式(guarded patterns)和带括号的模式(parenthesized patterns),其中保护模式允许使用任意的布尔表达式细化模式匹配的逻辑,带括号的模式用于解决歧义问题。
概述 - Java 17 中 switch 模式匹配新特性
-
增强的类型检查
- switch支持更多的类型:整型原始类型或任何引用类型
- 子类型的case必须出现在父类型case的前面
-
switch 表达式和语句的完整性
- 使用模式或null标签或其选择器表达式不是下面这些类型之一(char、byte、short、int、Character、Byte、Short、Integer、String 或 enum 类型)的 switch 语句需要保证完整性。
-
模式变量声明的范围
switch 的 case 标签的模式变量声明的范围包括:
- 出现在箭头右侧的表达式、块或 throw 语句。
- 出现在冒号右侧的语句组的块语句。
-
处理null
- 如果选择器表达式的计算结果为 null,如果能匹配某个case,则执行相应的代码;如果没有匹配的case,则像以前一样抛出 NullPointerException。
- case null 可以与其他模式或default组合使用
-
保护模式(guarded patterns)和括号模式(parenthesized patterns)
- 保护模式允许使用任意的布尔表达式细化模式匹配的逻辑
- 括号模式用于解决歧义问题。
详解
首先了解Java 17 中的switch与其他版本的区别
与Java 14的switch的区别
Java 14中:switch只能用于几种类型的值——数字类型、枚举类型和字符串,而且只能测试常量是否完全相等。
Java 17中:switch语句和表达式支持任何类型,case不仅支持常量,还支持模式(保护模式和括号模式)。
Java 16 中的 switch 块支持两种样式
一种基于标记的语句组(: 形式),其中可以fallthrough(没遇到break之前可以继续执行下一个case),另一种基于单一后项(single-consequent)形式(-> 形式),其中不可以fallthrough。在前一种风格中,多个标签通常写成 case l1: case l2: 而在后一种风格中,多个标签写成 case l1, l2:。
下面解释上面提到的新特性
1、增强类型检查
选择器表达式类型
普通switch的选择器表达式的类型必须是整型原始类型(char、byte、short 或 int)、相应的封装类型(Character、Byte、Short 或 Integer)、String 或枚举类型。java 17对此进行了扩展,并要求选择器表达式的类型是整型原始类型或任何引用类型。
record Point(int i, int j) {}
enum Color { RED, GREEN, BLUE; }
static void typeTester(Object o) {
switch (o) {
case null -> System.out.println("null");
case String s -> System.out.println("String");
case Color c -> System.out.println("Color with " + Color.values().length + " values");
case Point p -> System.out.println("Record class: " + p.toString());
case int[] ia -> System.out.println("Array of ints of length" + ia.length);
default -> System.out.println("Something else");
}
}
模式标签的主导地位
如果 switch 块仅包含类型模式的case标签,子类型的case必须出现在父类型case的前面。如下错误的代码:CharSequence 是String 的父接口,所以case String s 必须在 case CharSequence cs 前面。
static void error(Object o) {
switch(o) {
case CharSequence cs ->
System.out.println("A sequence of length " + cs.length());
case String s -> // Error - pattern is dominated by previous pattern
System.out.println("A string: " + s);
default -> {
break;
}
}
}
2、switch 表达式和语句中模式标签的完整性
case必须覆盖选择器表达式的所有可能值。例如:前两个case覆盖了String和Integer类型,default覆盖了剩余所有类型,如果没有default会发生编译错误:‘switch’ statement does not cover all possible input values
static int coverage(Object o) {
return switch (o) {
case String s -> s.length();
case Integer i -> i;
default -> 0;
};
}
但是如果一个switch块有多个全匹配(total pattern)标签(例如 case Object 和 default同时出现),这也是一个编译时错误。
再看一个密封类的例子:由于选择器表达式的类型 S 是一个密封接口,其允许的子类恰好是 A、B 和 C,所以这个switch块是完整的。因此,不需要default标签。
sealed interface S permits A, B, C {}
final class A implements S {}
final class B implements S {}
record C(int i) implements S {} // Implicitly final
static int testSealedCoverage(S s) {
return switch (s) {
case A a -> 1;
case B b -> 2;
case C c -> 3;
};
}
使用模式或null标签或其选择器表达式不是下面这些类型之一(char、byte、short、int、Character、Byte、Short、Integer、String 或 enum 类型)的 switch 语句需要保证完整性。
3、模式变量声明的作用域
- switch 的 case 标签的模式变量声明的范围包括:出现在箭头右侧的表达式、块或 throw 语句。
- switch 的 case 标签的模式变量声明的范围包括:出现在冒号右侧的语句组的块语句。
第一条规则的代码如下:模式变量 c 的声明范围是第一个箭头右侧的块。模式变量 i 的声明范围是第二个箭头右侧的 throw 语句。
static void test(Object o) {
switch (o) {
case Character c -> {
if (c.charValue() == 7) {
System.out.println("Ding!");
}
System.out.println("Character");
}
case Integer i ->
throw new IllegalStateException("Invalid Integer argument of value " + i.intValue());
default -> {
break;
}
}
}
第二条规则的代码如下:模式变量c的声明范围包括语句组(该case标签中)的所有语句,即两个if语句和println语句。c的范围不包括default语句组的语句,即使执行完第一个语句组后会继续执行default标签中的语句。
static void test(Object o) {
switch (o) {
case Character c:
if (c.charValue() == 7) {
System.out.print("Ding ");
}
if (c.charValue() == 9) {
System.out.print("Tab ");
}
System.out.println("character");
default:
System.out.println();
}
}
需要注意的是,冒号形式可能导致“Illegal fall-through to a pattern”编译时异常,即有可能进入下一个case标签的模式变量的声明会导致编译错误。如下代码,如果 o 的值是一个Character ,那么 switch 块的执行可能会进入 case Integer i,其中模式变量 i 不会被初始化。(在case Character c:最后一行加入break就不报错了)
static void test(Object o) {
switch (o) {
case Character c:
if (c.charValue() == 7) {
System.out.print("Ding ");
}
if (c.charValue() == 9) {
System.out.print("Tab ");
}
System.out.println("character");
case Integer i: // Compile-time error
System.out.println("An integer " + i);
default:
break;
}
}
4、处理null
null的匹配规则
如果选择器表达式的值为空,以前的switch立即抛出 NullPointerException 。但在Java 17 中会根据case确定switch的行为:
- 如果选择器表达式的计算结果为 null,则任何 case null 标签或总模式标签(total pattern)都被认为是匹配的。如果没有与 switch 块相关联的标签,则 switch 会像以前一样抛出 NullPointerException。注意default不匹配null。
- 如果选择器表达式的计算结果为非空值,那么我们会像往常一样选择匹配的 case 标签。如果没有 case 标签匹配,则任何全匹配(match-all label)标签(比如default)都被认为匹配。
对于第一条规则,参考下面的代码:当o为null时,输出null!
static void test(Object o) {
switch (o) {
case null -> System.out.println("null!");
case String s -> System.out.println("String");
default -> System.out.println("Something else");
}
}
当o为null时,下面的代码输出Something else。没有case null不一定会抛异常,只要有匹配的case就不会抛异常。
static void test(Object o) {
switch (o) {
case String s -> System.out.println("String");
case Object ob -> System.out.println("Something else");
}
}
当o为null时,因为没有与 switch 块相关联的标签,所有 switch 会像以前一样抛出 NullPointerException。
static void test(Object o) {
switch (o) {
case String s -> System.out.println("String");
default -> System.out.println("Something else");
}
}
4b.null与其他模式的组合形式
例如null与String组合,冒号形式和箭头形式都支持
Object o = ...
switch(o) {
case null: case String s:
System.out.println("String, including null");
break;
...
}
例如null与default组合,冒号形式和箭头形式都支持
Object o = ...
switch(o) {
...
case null, default ->
System.out.println("The rest (including null)");
}
5、保护模式和括号模式(Guarded and parenthesized patterns)
保护模式
保护模式的形式为 p && e,其中 p 是模式,e 是布尔表达式。在受保护模式中,任何使用但未在子表达式中声明的局部变量、形式参数或异常参数必须是最终(final)的或有效的最终的(effectively final:该变量未使用final修饰,但是其值在初始化后永远不会改变)。
p 中的任何模式变量声明的范围都包括表达式 e。这允许诸如 String s && (s.length() > 1) 之类的模式,它匹配可以强制转换为 String 的值,使得字符串的长度大于 1。
static void test(Object o) {
switch (o) {
case String s && (s.length() == 1) -> ...
case String s -> ...
...
}
}
括号模式
括号模式的形式为 §,其中 p 是模式。带括号的模式 § 引入了由子模式 p 引入的模式变量。如果一个值与模式 p 匹配,则它与带括号的模式 § 匹配。
如表达式 e instanceof String s && s.length() > 1 会解析为表达式 (e instanceof String s ) && (s.length() > 1)。如果 想让&& 成为受保护模式的一部分,则整个模式应该用括号括起来,例如 e instanceof (String s && s.length() > 1)。