0%

Java语言新特性漫谈:Java 17篇

密封类

该特性在 Java 15 中引入,Java 16 继续作为预览特性,Java 17 终于转正。

密封类(Sealed Class),或接口限制了哪些类或接口可以扩展或实现它。

可以参考《Java语言新特性漫谈:Java 15篇》《Java语言新特性漫谈:Java 16篇》相关章节。较 Java 16 而言,正式版本并未有更新。

switch中的模式匹配(预览特性)

Pattern matching for switch expressions and statements allows an expression to be tested against a number of patterns, each with a specific action, so that complex data-oriented queries can be expressed concisely and safely.

模式匹配特性在 Java 14 引入,历经 Java 15,在 Java 16 成为正式版。

Switch 表达式特性从 Java 12 引入,历经 Java 13,在 Java 14 成为正式版。

本特性是模式匹配在 Switch 表达式中的应用,是其首个预览版本。

模式匹配最早应用于 instanceof 操作符。

一个典型的应用场景是,通过判断一个变量的类型然后进行强转再进行相关处理。

使用 instanceof 模式匹配也需要写一长串的判断,类似下面的例子:

1
2
3
4
5
6
7
if (shape instanceof Rectangle r) {
return 2 * r.length() + 2 * r.width();
} else if (shape instanceof Circle c) {
return 2 * c.radius() * Math.PI;
} else {
throw new IllegalArgumentException("Unrecognized shape");
}

更不要说不使用 instanceof 模式匹配还要冗长的强转了。

然而由于 switch 语句的局限性,上面的程序片断还不能转换为 switch 语句——但是,当引入模式匹配特性让这一切成为可能。

模式匹配的 switch 表达式改写如下:

1
2
3
4
5
6
7
public static double getPerimeter(Shape shape) throws IllegalArgumentException {
return switch (shape) {
case Rectangle r -> 2 * r.length() + 2 * r.width();
case Circle c -> 2 * c.radius() * Math.PI;
default -> throw new IllegalArgumentException("Unrecognized shape");
};
}

模式匹配用以抽取转换后的具体子类型变量,更方便了进一步处理。当然也可以写为 switch 语句的形式:

1
2
3
4
5
6
7
public static double getPerimeter(Shape shape) throws IllegalArgumentException {
switch (shape) {
case Rectangle r: return 2 * r.length() + 2 * r.width();
case Circle c: return 2 * c.radius() * Math.PI;
default: throw new IllegalArgumentException("Unrecognized shape");
}
}

条件分支测试的顺序性

switch 模式匹配在执行上与普通的 switch 语句并无不同,都是按声明顺序逐一测试执行的。

由于类型间存在父子关系,因此条件分支间显然存在包含关系。如果一个被包含的条件分支位于包含它的条件分支之后,那么,它将永不可达。这称为“模式标签优势(Pattern Label Dominance)”——这将导致编译错误

模式标签优势(Pattern Label Dominance)的官方表述为:某个模式标签总是先匹配,而导致后续某个标签总不会匹配。
另外要注意,默认标签虽然可以匹配任何值,但是它不具有“优势”,不会导致其他标签不被匹配。理论上,默认标签放在任何位置都是等价的,只是习惯上将其放在最后。

条件匹配的全面性

switch 表达式应是全面的(exhaustive),它应能匹配处理所有可能的值。否则将是个编译错误

因此,通常需要列出所以可能的条件分支。对于密封类而言,这是可能的。但是“普通”类型往往是无法穷举的,因此需要其他方式来处理所有剩余的类型,下面会提及。

对于密封类而言,只需要列出所有允许的子类(因此默认标签不是必须的)。

但普通类型通常不能逐一列出,所以往往需要统一处理所有剩余类型。
有两个标签会匹配所有的值,即默认标签和总模式(total type pattern)。因此,它们是互斥的,否则编译错误。

所谓“总模式”,是指条件类型为 switch 判断的传入类型或其父类型。比如在上文示例中:case Shape s 就是一个总模式。
注意,由于“优势”的存在,总模式通常置于最后,以免导致后续分支不可达。

模式变量的作用域

switch 模式匹配中的模式变量的作用域根据条件分支写法不同而不同——新增的 arrow case 语法和老式的 colon case 语法。

  • 使用箭头 caseswitch 语句中的模式变量可以在箭头右侧使用。

注意,箭头 case 不存在“贯穿”。

  • 使用冒号 caseswitch 语句中,即使存在“贯穿”也不能在其他 case 标签中使用当前标签的模式变量,否则编译错误;反过来说,如果存在“贯穿”,则被贯穿的标签模式变量没有初始化,因而导致编译错误;同样,试图声明多个模式变量,也会存在某个变量没有初始化的问题。

注意,同一 switch 中,两种 case 不能混用。

匹配null

case null 用以匹配 null 值。总模式也会匹配 null 值。

为了使 case null 不被总模式拦截,应将其置于总模式前。

如果选择器表达式计算结果是 null,但 switch 没有 null 匹配,则抛“空指针”。

参考