JEP 406:开关的模式匹配(预览)
概括
switch
通过表达式和语句的模式匹配以及模式语言的扩展来增强 Java 编程语言。扩展模式匹配允许switch
针对多个模式测试表达式,每个模式都有一个特定的操作,以便可以简洁、安全地表达复杂的面向数据的查询。这是JDK 17 中的预览语言功能。
目标
-
switch
通过允许模式出现在case
标签中来扩展表达式和语句的表现力和适用性。 -
switch
在需要时允许放松历史上的零敌意。 -
引入两种新的模式:受保护的模式,允许使用任意布尔表达式来细化模式匹配逻辑,以及_括号模式_,以解决一些解析歧义。
-
确保所有现有
switch
表达式和语句继续编译而不进行任何更改并以相同的语义执行。 -
不要引入
switch
与传统构造分离的具有模式匹配语义的新的类似表达式或语句switch
。 -
switch
当 case 标签是模式时与 case 标签是传统常量时,不要使表达式或语句的行为有所不同。
动机
在 Java 16 中,JEP 394扩展了该instanceof
运算符以采用_类型模式_并执行_模式匹配_。这个适度的扩展允许简化熟悉的 instanceof-and-cast 习惯用法:
// Old code
if (o instanceof String) {
String s = (String)o;
... use s ...
}
// New code
if (o instanceof String s) {
... use s ...
}
我们经常想要将一个变量与o
多个替代方案进行比较。 Java 支持switch
语句和switch
表达式(JEP 361 )的多路比较,但不幸的switch
是非常有限。您只能打开几种类型的值 - 数字类型、枚举类型等String
- 并且只能测试与常量的精确相等。我们可能想使用模式来测试同一个变量的多种可能性,对每种可能性采取特定的操作,但由于现有的switch
不支持这一点,我们最终会得到一系列if...else
测试,例如:
static String formatter(Object o) {
String formatted = "unknown";
if (o instanceof Integer i) {
formatted = String.format("int %d", i);
} else if (o instanceof Long l) {
formatted = String.format("long %d", l);
} else if (o instanceof Double d) {
formatted = String.format("double %f", d);
} else if (o instanceof String s) {
formatted = String.format("String %s", s);
}
return formatted;
}
该代码受益于使用模式instanceof
表达式,但它远非完美。首先也是最重要的,这种方法可以隐藏编码错误,因为我们使用了过于通用的控制结构。目的是为链formatted
的每个分支分配一些内容if...else
,但没有任何东西可以使编译器识别和验证此不变量。如果某个块(也许是很少执行的块)没有分配给formatted
,我们就会遇到一个错误。 (声明formatted
为空白局部变量至少会在这项工作中使用编译器的明确赋值分析,但并不总是编写这样的声明。)此外,上述代码不可优化;如果没有编译器英雄,它将具有_O_ ( n ) 时间复杂度,即使底层问题通常是_O_ (1)。
但switch
对于图案搭配来说却是绝配!如果我们扩展switch
语句和表达式以适用于任何类型,并允许使用case
模式标签而不仅仅是常量,那么我们可以更清晰可靠地重写上面的代码:
static String formatterPatternSwitch(Object o) {
return switch (o) {
case Integer i -> String.format("int %d", i);
case Long l -> String.format("long %d", l);
case Double d -> String.format("double %f", d);
case String s -> String.format("String %s", s);
default -> o.toString();
};
}
其语义switch
很明确:如果值case
与模式匹配,则具有模式的标签与选择器表达式的值匹配。 o
(为了简洁起见,我们显示了一个switch
表达式,但也可以显示一个switch
语句;switch 块(包括case
标签)将保持不变。)
这段代码的意图 更加清晰,因为我们使用了正确的控制结构:我们是说,“参数o
最多匹配以下条件之一,找出并评估相应的手臂。”作为奖励,它是可优化的;在这种情况下,我们更有可能能够在_O_ (1) 时间内执行调度。
模式匹配和null
传统上,如果选择器表达式的计算结果为 ,则switch
语句和表达式会抛出异常,因此必须在 之外进行测试:NullPointerException``null``null``switch
static void testFooBar(String s) {
if (s == null) {
System.out.println("oops!");
return;
}
switch (s) {
case "Foo", "Bar" -> System.out.println("Great");
default -> System.out.println("Ok");
}
}
switch
当仅支持少数引用类型时,这是合理的。然而,如果switch
允许任何类型的选择器表达式,并且case
标签可以具有类型模式,那么独立null
测试感觉像是任意的区别,并且会带来不必要的样板文件和出错的机会。最好将null
测试集成到switch
:
static void testFooBar(String s) {
switch (s) {
case null -> System.out.println("Oops");
case "Foo", "Bar" -> System.out.println("Great");
default -> System.out.println("Ok");
}
}
switch
当选择器表达式的值为 时,其行为null
始终由其标签决定case
。使用 a case null
(或总体类型模式;参见下面的 4a),switch
执行与该标签关联的代码;如果没有 a case null
,则switch
抛出NullPointerException
,就像以前一样。 (为了保持与当前语义的向后兼容性switch
,default
标签与选择器不匹配null
。)
我们不妨以与另一个标签null
相同的方式来处理。case
例如,在以下代码中,case null, String s
将匹配该null
值和所有String
值:
static void testStringOrNull(Object o) {
switch (o) {
case null, String s -> System.out.println("String: " + s);
}
}
细化模式switch
对模式的实验switch
表明,想要改进模式是很常见的。考虑以下切换值的代码Shape
:
class Shape {}
class Rectangle extends Shape {}
class Triangle extends Shape { int calculateArea() { ... } }
static void testTriangle(Shape s) {
switch (s) {
case null:
break;
case Triangle t:
if (t.calculateArea() > 100) {
System.out.println("Large triangle");
break;
}
default:
System.out.println("A shape, possibly a small triangle");
}
}
此代码的目的是为大三角形(面积超过 100)提供特殊情况,为其他所有内容(包括小三角形)提供默认情况。然而,我们不能用单一模式直接表达这一点。我们首先必须编写一个case
匹配所有三角形的标签,然后将三角形面积的测试相当不舒服地放在相应的语句组中。然后,当三角形的面积小于 100 时,我们必须使用 drop-through 来获得正确的行为。(请注意块break;
内部的仔细放置if
。)
这里的问题是,使用单一模式来区分案例并不能超出单一条件。我们需要某种方式来表达对模式的_细化_。一种方法可能是允许case
细化标签;这种细化在其他编程语言中称为_防护_。例如,我们可以引入一个新关键字where
出现在标签末尾case
,并后跟一个布尔表达式,例如case Triangle t where t.calculateArea() > 100
。
然而,还有一种更具表现力的方法。case
我们可以扩展模式本身的语言,而不是扩展标签的功能。我们可以添加一种新的模式,称为_受保护模式_,它允许通过任意布尔表达式来细化p && b
模式。p``b
通过这种方法,我们可以重新访问testTriangle
代码来直接表达大三角 形的特殊情况。这消除了在语句中使用 drop-through 的情况switch
,这又意味着我们可以享受简洁的箭头式 ( ->
) 规则:
static void testTriangle(Shape s) {
switch (s) {
case Triangle t && (t.calculateArea() > 100) ->
System.out.println("Large triangle");
default ->
System.out.println("A shape, possibly a small triangle");
}
}
如果的值首先与类型模式s
匹配,则该值与模式匹配,如果是,则表达式的计算结果为。Triangle t && (t.calculateArea() > 100)``Triangle t``t.calculateArea() > 100``true
当应用程序需求发生变化时,使用switch
它可以轻松理解和更改案例标签。例如,我们可能想从默认路径中分割出三角形;我们可以通过使用精炼模式和非精炼模式来做到这一点:
static void testTriangle(Shape s) {
switch (s) {
case Triangle t && (t.calculateArea() > 100) ->
System.out.println("Large triangle");
case Triangle t ->
System.out.println("Small triangle");
default ->
System.out.println("Non-triangle");
}
}
描述
我们switch
通过两种方式增强语句和表达式:
-
扩展
case
标签以包含除常量之外的模式,以及 -
引入两种新的模式:保护模式_和_括号模式。
开关标签中的模式
该提案的核心是引入一个新的case p
开关标签,其中p
是一个模式。然而,a 的本质switch
没有改变:将选择器表达式的值与开关标签进行比较,选择其中一个标签,并执行与该标签关联的代码。现在的区别在于,对于case
具有模式的标签,该选择是通过模式匹配而不是通过相等性检查来确定的。例如,在以下代码中, 的值与o
模式 匹配Long l
,并且 关联的代码case Long l
将被执行:
Object o = 123L;
String formatted = switch (o) {
case Integer i -> String.format("int %d", i);
case Long l -> String.format("long %d", l);
case Double d -> String.format("double %f", d);
case String s -> String.format("String %s", s);
default -> o.toString();
};
case
当标签具有图案时,存在四个主要设 计问题:
- 增强类型检查
switch
表达式和陈述的完整性- 模式变量声明的范围
- 处理
null
1. 增强类型检查
1a.选择器表达式输入
支持 in 模式switch
意味着我们可以放宽当前对选择器表达式类型的限制。目前,法线的选择器表达式的类型switch
必须是整型原始类型(char
、byte
、short
或int
)、相应的装箱形式(Character
、Byte
、Short
或Integer
)、String
或枚举类型。我们对此进行了扩展,并要求选择器表达式的类型可以是整型原始类型,也可以是任何引用类型。
例如,在以下模式中,switch
选择器表达式与涉及类类型、枚举类型、记录类型和数组类型(以及标签和 a )的o
类型模式匹配:null
case``default
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
带有模式的标签(称为_模式标签) ,我们使用__表达式与模式的兼容性_的现有概念(JLS §14.30.1)。
1b.图案标签的主导地位
选择器表达式可以匹配 switch 块中的多个标签。考虑这个有问题的例子:
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;
}
}
}
第一个模式标签case CharSequence cs
_支配_第二个模式标签,case String s
因为与该模式匹配的每个值String s
也与该模式匹配CharSequence cs
,但反之则不然。这是因为第二个模式 的类型String
是第一个模式 的类型的子类型CharSequence
。
形式的模式标签,case p
其中p
是选择器表达式类型的总模式,主导标签case null
。这是因为总模式匹配所有值,包括null
.
形式的模式标签case p
支配形式的模式标签case p && e
,即,其中模式是原始模式的受保护版本。例如,模式标签case String s
支配 模式标签case String s && s.length() > 0
,因为与受保护模式匹配的每个值String s && s.length() > 0
也与模式匹配String s
。
编译器检查所有模式标签。如果 switch 块中的模式标签由该 switch 块中较早的模式标签主导,则会出现编译时错误。
此主导要求确保如果 switch 块仅包含类型模式大小写标签,则这些标签将以子类型顺序出现。
支配的概念类似于
catch
语句子句的条件,如果捕获异常类的子句前面有一个可以捕获异常类的子句或( JLS §11.2.3 )的超类,try
则会出错。从逻辑上讲,前面的子句支配后面的子句。catch``E``catch``E``E``catch``catch
如果开关块具有多个全匹配开关标签,这也是一个编译时错误。两个_全匹配_标签是default
和 Total 类型模式(参见下面的 4a)。
2.switch
表达式和语句中模式标签的完整性
表达式switch
要求选择器表达式的所有可能值都在 switch 块中处理。这保持了switch
表达式的成功求值总是会产生一个值的属性。对于普通switch
表达式,这是通过 switch 块上一组相当简单的额外条件来强制执行的。对于模式表达式,我们定义了switch 块的_类型覆盖_switch
的概念。
考虑这个(错误的)模式switch
表达式:
static int coverage(Object o) {
return switch (o) { // Error - incomplete
case String s -> s.length();
};
}
开关块只有一个case
标签case String s
。这与类型为 的子类型的选择器表达式的任何值匹配String
。因此我们说这个箭头规则的类型覆盖是 的每个子类型String
。该模式switch
表达式是不完整的,因为其 switch 块的类型覆盖范围不包括选择器表达式的类型。
考虑这个 (仍然是错误的)例子:
static int coverage(Object o) {
return switch (o) { // Error - incomplete
case String s -> s.length();
case Integer i -> i;
};
}
该开关块的类型覆盖率是其两个箭头规则的覆盖率的并集。换句话说,类型覆盖率是 的所有子类型的集合String
和 的所有子类型的集合Integer
。但是,类型覆盖仍然不包括选择器表达式的类型,因此该模式switch
表达式也不完整并会导致编译时错误。
的 类型覆盖范围default
是所有类型,因此这个示例(终于!)合法:
static int coverage(Object o) {
return switch (o) {
case String s -> s.length();
case Integer i -> i;
default -> 0;
};
}
如果选择器表达式的类型是密封类(JEP 409),则类型覆盖检查可以考虑permits
密封类的子句来确定 switch 块是否完整。考虑以下接口示例,该sealed
接口S
具有三个允许的子类A
、B
和C
:
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;
};
}
编译器可以确定 switch 块的类型覆盖是类型A
, B
, 和C
。由于选择器表达式 , 的类型S
是一个密封接口,其允许的子类正是A
, B
, 和C
,因此这个 switch 块是完整的。因此,不需要default
标签。
为了防止不兼容的单独编译,编译器会自动添加一个default
标签,该标签的代码会抛出IncompatibleClassChangeError
.只有当sealed
接口改变并且switch
代码没有重新编译时才会到达这个标签。实际上,编译器会为您强化代码。
模式表达式完整的要求
switch
类似于switch
选择器表达式是枚举类的表达式的处理,其中default
如果枚举类的每个常量都有一个子句,则不需要标签。
switch
_让编译器验证表达式_是否完整的作用非常有用。我们不仅仅针对switch
表达式保留此检查,switch
还将其扩展到语句。出于向后兼容性的原因,所有现有switch
语句将按原样进行编译。但是,如果switch
语句使用此 JEP 中详细介绍的任何新功能,那么编译器将检查它是否完整。
switch
更准确地说,使用模式或null
标签或其选择器表达式不是旧类型(char
、byte
、short
、int
、Character
、Byte
、Short
、 、Integer
、String
或枚举类型)之一的语句需要完整性。
这意味着现在switch
表达式_和_ switch
语句都可以获得更严格的类型检查的好处。例如:
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 void switchStatementComplete(S s) {
switch (s) { // Error - incomplete; missing clause for permitted class B!
case A a :
System.out.println("A");
break;
case C c :
System.out.println("B");
break;
};
}
使大多数语句完整只需在 switch 主体末尾switch
添加一个简单的子句即可。default
这使得代码更清晰、更容易验证。例如,下面的switch
陈述是不完整的并且是错误的:
Object o = ...
switch (o) { // Error - incomplete!
case String s:
System.out.println(s);
break;
case Integer i:
System.out.println("Integer");
break;
}
它可以变得完整,简单:
Object o = ...
switch (o) {
case String s:
System.out.println(s);
break;
case Integer i:
System.out.println("Integer");
break;
default: // Now complete!
break;
}
未来的 Java 语言编译器可能会针对
switch
不完整的遗留语句发出警告。
3. 模式变量声明的范围
模式变量(JEP 394)是由模式声明的局部变量。模式变量声明的不同寻常之处在于它们的作用域是_流敏感的_。 回顾一下以下示例,其中类型模式String s
声明了模式变量s
:
static void test(Object o) {
if ((o instanceof String s) && s.length() > 3) {
System.out.println(s);
} else {
System.out.println("Not a string");
}
}
的声明s
位于表达式右侧操作数&&
以及“then”块的范围内。但是,它不在“else”块的范围内;为了将控制转移到“else”块,模式匹配必须失败,在这种情况下,模式变量将不会被初始化。
我们扩展了模式变量声明的这种流敏感的范围概念,以包含使用case
两个新规则的标签中出现的模式声明:
-
case
规则标签中出现的模式变量声明的范围包括出现在箭头右侧的switch
表达式、块或语句。throw
-
case
出现在带标签语句组的标签中的模式变量声明的范围switch
(后面没有其他switch
标签)包括该语句组的块语句。
此示例显示了第一条正在运行的规则:
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
是第一个箭头右侧的块。
模式变量声明的范围i
是throw
第二个箭头右侧的语句。
第二条规则更复杂。让我们首先考虑一个示例,其中带标签的语句组只有一个case
标签switch
:
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();
}
}
模式变量的声明范围c
包括语句组的所有语句,即两条if
语句和一条println
语句。该范围不包括该default
语句组的语句,尽管第一个语句组的执行可以通过default
switch 标签并执行这些语句。
必须将掉入声明模式变量的标签的可能性case
作为编译时错误排除。考虑这个错误的例子:
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;
}
}
如果允许这样做并且选择器表达式的值为o
a Character
,则 switch 块的执行可能会通过第二个语句组( after case Integer i:
),其中模式变量i
不会被初始化。因此,允许执行通过case
声明模式变量的标签是一个编译时错误。
这就是为什么case Character c: case Integer i: ...
不允许的原因。类似的推理也适用于禁止case
标签中使用多种模式:既不case Character c, Integer i: ...
也case Character c, Integer i -> ...
不允许。如果case
允许这样的标签,则c
和i
都将在冒号或箭头之后的范围内,但只有其中之一c
会i
被初始化,具体取决于 的值o
是 aCharacter
还是 an Integer
。
另一方面,穿过未声明模式变量的标签是安全的,如以下示例所示:
void test(Object o) {
switch (o) {
case String s:
System.out.println("A string");
default:
System.out.println("Done");
}
}
4. 处理null
4a.匹配null
传统上,如果选择器表达式的计算结果为 ,则switch
抛出a 。这是众所周知的行为,我们不建议对任何现有代码进行更改。NullPointerException``null``switch
然而,鉴于模式匹配和null
值存在合理且无异常的语义,因此有机会使模式switch
更加null
友好,同时保持与现有switch
语义的兼容。
首先,我们null
为 a 引入一个新标签case
,当选择器表达式的值为 时,它会清楚地匹配null
。
其次,我们观察到,如果选择器表达式类型的_总_模式出现模式case
标签,那么当选择器表达式的值为 时,该标签也将匹配null
。
如果_T是__U_的子类型,则类型_U_的类型模式 p是类型_T_的总和。例如,类型模式是类型的总和。
Object o``String
我们解除了如果选择器表达式的值为 则aswitch
立即抛出的总括规则。相反,我们检查标签来确定 a 的行为:NullPointerException``null``case``switch
-
如果选择器表达式的计算结果为,
null
则认为任何null
case 标签或总模式 case 标签匹配。如果没有与 switch 块关联的此类标签,则像以前一样switch
抛出。NullPointerException
-
如果选择器表达式的计算结果为非值,
null
那么我们case
照常选择一个匹配的标签。如果没有case
标签匹配,则任何匹配所有标签都被视为匹配。
例如,给出下面的声明,评估test(null)
将打印null!
而不是抛出NullPointerException
:
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");
}
}
这种新行为就好像编译器自动用其主体抛出null
异常来丰富 switch 块。换句话说,这段代码:case null``NullPointerException
static void test(Object o) {
switch (o) {
case String s -> System.out.println("String: " + s);
case Integer i -> System.out.println("Integer");
default -> System.out.println("default");
}
}
相当于:
static void test(Object o) {
switch (o) {
case null -> throw new NullPointerException();
case String s -> System.out.println("String: "+s);
case Integer i -> System.out.println("Integer");
default -> System.out.println("default");
}
}
在这两个示例中,评估test(null)
都会导致NullPointerException
抛出。
我们保留了现有结构的直觉switch
,即执行切换null
是一件特殊的事情。模式的区别switch
在于,您有一种机制可以在内部switch
而不是外部直接处理这种情况。如果您选择在 switch 块中不包含空匹配case
标签,则切换null
值将像以前一样抛出NullPointerException
。
4b.null
标签产生的新标签形式
JDK 16 中的 Switch 块支持两种样式:一种基于带标签的语句组(形式:
),其中可以进行回落,另一种基于单结果形式(形式->
),其中不可能进行回落。在前一种风格中,通常写入多个标签,case l1: case l2:
而在后一种风格中,通常写入多个标签case l1, l2:
。
支持null
标签意味着可以在表单中表达许多特殊情况:
。例如:
Object o = ...
switch(o) {
case null: case String s:
System.out.println("String, including null");
break;
...
}
人们期望:
和->
应该具有同等的表达能力,并且如果case A: case B:
前一种风格支持,那么case A, B ->
后一种风格也应该支持。因此,前面的示例建议我们应该支持标签case null, String s ->
,如下所示:
Object o = ...
switch(o) {
case null, String s -> System.out.println("String, including null");
...
}
_当 的值是空引用或者_是时,它的值o
与该标签匹配。在这两种情况下,模式变量都使用 的值进行初始化。String``s``o
(相反的形式case String s, null
也应该被允许并且行为相同。)
null
将案例与标签结合起来也是有意义的(并且并不罕见)default
,即
Object o = ...
switch(o) {
...
case null: default:
System.out.println("The rest (including null)");
}
同样,这应该以形式得到支持->
。为此,我们引入了一个新的default
案例标签:
Object o = ...
switch(o) {
...
case null, default ->
System.out.println("The rest (including null)");
}
_如果 的_值是空参考值,或者没有其他标签匹配,则的值o
与该标签匹配。
保护模式和括号模式
成功的模式匹配后,我们通常会进一步测试匹配的结果。这可能会导致代码变得繁琐,例如:
static void test(Object o) {
switch (o) {
case String s:
if (s.length() == 1) { ... }
else { ... }
break;
...
}
}
不幸的是,所需的测试(即长度为 1 的o
a String
)被分割在case
标签和随后的if
语句之间。如果模式支持标签switch
中模式和布尔表达式的组合,我们可以提高可读性。case
我们不是添加另一个特殊标签,而是通过添加_受保护的模式_(书面)case
来增强模式语言。这允许重写上面的代码,以便将所有条件逻辑提升到标签中:p && e``case
static void test(Object o) {
switch (o) {
case String s && (s.length() == 1) -> ...
case String s -> ...
...
}
}
第一种情况匹配 ifo
既是 aString
_且_长度为 1。第二种情况匹配 if是其他长度的o
a 。String
有时我们需要对模式加上括号以避免解析歧义。因此,我们扩展了模式语言以支持编写的括号模式(p)
,其中p
是模式。
更准确地说,我们改变了模式的语法。假设添加JEP 405的记录模式和数组模式,模式的语法将变为:
Pattern:
PrimaryPattern
GuardedPattern
GuardedPattern:
PrimaryPattern && ConditionalAndExpression
PrimaryPattern:
TypePattern
RecordPattern
ArrayPattern
( Pattern )
_受保护模式_的形式为p && e
,其中p
是模式,e
是布尔表达式。在受保护模式中,任何在子表达式中使用但未声明的局部变量、形式参数或异常参数都必须是final
最终的或实际上是最终的。
受保护的模式引入了由pattern和表达式p && e
引入的模式变量的并集。中的任何模式变量声明的范围都包括表达式。这允许使用诸如 之类的模式,它匹配一个可以转换为 a 的值,使得字符串的长度大于 1。p``e``p``e``String s && (s.length() > 1)``String
如果一个值与受保护的模式匹配p && e
,首先它与该模式匹配p
,其次该表达式的e
计算结果为true
。如果值不匹配,p
则不会尝试计算表达式e
。
_带括号的模式_的形式为(p)
,其中p
是模式。带括号的模式(p)
引入了由子模式引入的模式变量p
。(p)
如果值与模式匹配,则该值与带括号的模式匹配p
。
我们还将表达式的语法更改instanceof
为:
InstanceofExpression:
RelationalExpression instanceof ReferenceType
RelationalExpression instanceof PrimaryPattern
例如,此更改以及受保护模式的语法规则中的非终结符ConditionalAndExpression
确保表达式e instanceof String s && s.length() > 1
继续明确地解析为表达式(e instanceof String s) && (s.length() > 1)
。如果尾部&&
旨在成为受保护模式的一部分,则整个模式应加括号,例如e instanceof (String s && s.length() > 1)
。
ConditionalAndExpression
在受保护模式的语法规则中使用非终结符还消除了关于case
具有受保护模式的标签的另一个潜在的歧义。例如:boolean b = true;
switch (o) {
case String s && b -> s -> s;
}
->
如果允许受保护模式的保护表达式是任意表达式,那么第一次出现的是 lambda 表达式的一部分还是 switch 规则的一部分(其主体是 lambda 表达式)就会产生歧义。由于 lambda 表达式永远不可能是有效的布尔表达式,因此限制保护表达式的语法是安全的。
未来的工作
-
目前,模式
switch
不支持基本类型boolean
、float
和double
。它们的效用似乎很小,但可以添加对它们的支持。 -
我们期望,将来,通用类将能够声明解构模式来指定它们如何匹配。这种解构模式可以与模式一起使用
switch
来生成非常简洁的代码。例如,如果我们有一个Expr
包含子类型 forIntExpr
(包含单个int
)、AddExpr
andMulExpr
(包含两个Expr
s)和NegExpr
(包含单个)的层次结 构,我们可以一步Expr
匹配并作用于特定子类型:Expr
int eval(Expr n) {
return switch(n) {
case IntExpr(int i) -> i;
case NegExpr(Expr n) -> -eval(n);
case AddExpr(Expr left, Expr right) -> eval(left) + eval(right);
case MulExpr(Expr left, Expr right) -> eval(left) * eval(right);
default -> throw new IllegalStateException();
};
}如果没有这种模式匹配,像这样表达临时多态计算需要使用麻烦的[访问者模式][访问者]。模式匹配通常更加透明和直接。
-
添加 AND 和 OR 模式也可能很有用,以便为
case
具有模式的标签提供更多表现力。
备择方案
-
switch
我们可以定义一个仅支持切换选择器表达式类型的_类型switch
_,而不是支持模式。此功能更易于指定和实现,但表现力却相当低。 -
受保护模式还有许多其他语法选项,例如
p where e
、p when e
、p if e
、 甚至p &&& e
。 -
保护模式的替代方案是直接支持_保护_作为特殊形式的
case
标签:SwitchLabel:
case Pattern [ when Expression ]
...支持
case
标签中的防护需要引入when
新的上下文关键字,而防护模式不需要新的上下文关键字或运算符。受保护模式提供了更大的灵活性,因为受保护模式可以出现在其应用的位置附近,而不是出现在开关标签的末尾。
依赖关系
该 JEP 基于instanceof
( JEP 394 ) 的模式匹配以及switch
表达式 ( JEP 361 ) 提供的增强功能。我们希望此 JEP 与JEP 405一致,后者定义了两种支持嵌套的新模式。该实现可能会使用动态常量(JEP 309)。