Java 21 中 Switch 的模式匹配

2024年9月10日 | 阅读 6 分钟

模式匹配(Pattern matching)是 **Java 21** 中引入的对 switch 表达式和语句的一项功能,它允许开发者在 switch 的 case 中匹配特定的模式,使代码更简洁、可读性更强。

要使用 switch 中的模式匹配,我们只需使用 case 关键字后跟一个模式。该模式将与选择器表达式进行匹配,如果匹配成功,则执行相应的代码块。

在之前的 Java 版本中,switch 语句的使用仅限于选择器表达式求值为数字、字符串或枚举常量的情况。此外,case 标签必须是常量。然而,在本次发布中,我们可以使用任何类型的选择器表达式的 switch 语句或表达式,并且 case 标签可以包含模式。

这项增强功能意味着 switch 语句或表达式现在可以检查其选择器表达式是否与模式匹配,而不仅仅是与常量值进行比较。这在基于选择器表达式的值控制执行流程方面提供了灵活性和表现力。例如:

PatternMatchingExample.java

输出

obj is a String: Hello, world!

选择器表达式类型

选择器表达式的类型可以是整型基本类型,也可以是任何引用类型,如前例所示。以下 switch 表达式使用类型模式匹配选择器表达式 obj,这些模式涉及类类型、枚举类型、记录类型和数组类型。

When 子句

When 子句(when clause)允许使用布尔表达式来细化模式。包含 when 子句的模式标签称为守卫模式标签 (guarded pattern label),when 子句中的布尔表达式称为守卫 (guard)。当一个值匹配守卫模式标签时,意味着它匹配该模式并且守卫评估为 true。请考虑以下示例:

以下 switch 语句使用 when 子句匹配一个包含至少一个偶数的整数列表:

模式标签的支配关系

Java 21 中模式匹配的模式标签支配关系(Pattern label dominance)是一种机制,用于确定当多个模式匹配选择器表达式时,哪个 case 标签应该被执行。

有可能多个模式标签会匹配选择器表达式的值。为了提高可预测性,标签的测试顺序与它们在 switch 块中出现的顺序相同。此外,如果一个模式标签永远无法匹配,因为前面有一个标签总是会先匹配,则编译器会报错。以下示例会导致编译时错误:

以下规则用于确定模式标签的支配关系:

  • 模式标签支配常量标签。例如,模式标签 case String s 支配常量标签 case "hello"。
  • 如果同一个模式标签(不带守卫)支配另一个模式标签,则守卫模式标签也支配该常量标签。例如,模式标签 case String s when s.length() > 1 支配常量标签 case "hello"。
  • 无守卫的模式标签支配具有相同模式的守卫模式标签。例如,模式标签 case String s 支配模式标签 case String s when s.length() > 0。
  • 守卫模式标签支配另一个模式标签(守卫或无守卫),仅当前者模式支配后者模式,并且其守卫是值为 true 的常量表达式时。

如果多个模式匹配选择器表达式,并且没有模式支配彼此,则编译器将发出错误。

Switch 表达式和语句中的类型覆盖

如 switch 表达式中所述,使用模式或 null 标签的 switch 表达式和 switch 语句的 switch 块必须是穷尽的(exhaustive)。这意味着对于所有可能的值,都必须有一个匹配的 switch 标签。以下 switch 表达式不是穷尽的,并会生成编译时错误。它的类型覆盖仅包括 String 或 Integer 的子类型,而不包括选择器表达式的类型。例如:

HelloWorld.java

输出

obj is a String

模式变量声明的作用域

模式变量声明的作用域是程序中模式变量可访问的部分。在 switch 语句中声明的模式变量的作用域是 switch 块以及 switch 块内的任何嵌套块。

以下是一个 switch 语句中模式变量声明作用域的示例:

PatternVariableScopeExample.java

输出

The length of the string is: 12

在此预览功能之前,如果选择器表达式的值为 null,switch 表达式和 switch 语句会抛出 NullPointerException。然而,为了增加灵活性,提供了一个 null case 标签。

HelloWorld.java

输出

obj is null

括号模式

括号模式(Parenthesized pattern)是被一对括号括起来的模式。由于守卫模式结合了模式和表达式,可能会引入解析歧义。我们可以用括号括起模式来避免这些歧义,强制编译器以不同的方式解析包含模式的表达式,或提高代码的可读性。

HelloWorld.java

输出

obj is null