跳到主要内容

JEP 394:instanceof 的模式匹配

概括

通过运算符的_模式匹配_增强 Java 编程语言instanceof模式匹配允许更简洁、更安全地表达程序中的通用逻辑,即从对象中条件提取组件。

历史

模式匹配由JEP 305instanceof提出,并作为预览功能在JDK 14中提供。它由JEP 375重新提出,并在JDK 15中交付,进行第二轮预览。

此 JEP 建议最终确定 JDK 16 中的该功能,并进行以下改进:

  • 取消模式变量隐式最终的限制,以减少局部变量和模式变量之间的不对称性。

  • 将Sinstanceof类型的表达式与_T_类型的模式进行比较时,将模式表达式设置为编译时错误,其中_S是__T_的子类型。 (这个表达式总是成功,然后就没有意义了。相反的情况,模式匹配总是失败,已经是一个编译时错误。)instanceof

根据进一步的反馈,可能会合并其他改进。

动机

几乎每个程序都包含某种逻辑,这些逻辑结合了测试表达式是否具有某种类型或结构,然后有条件地提取其状态的组成部分以进行进一步处理。例如,所有 Java 程序员都熟悉instanceof-and-cast 习惯用法:

if (obj instanceof String) {
String s = (String) obj; // grr...
...
}

这里发生了三件事:测试(是objString)、转换(转换objString)以及声明新的局部变量 ( s),以便我们可以使用字符串值。这种模式很简单,所有 Java 程序员都可以理解,但由于多种原因,它并不是最理想的。这很乏味;不需要同时进行类型测试和强制类型转换(instanceof测试后您还会做什么?)。这个样板文件——特别是该类型的三次出现String——混淆了接下来的更重要的逻辑。但最重要的是,重复为错误在不被注意的情况下潜入程序提供了机会。

我们认为,Java 是时候拥抱模式匹配了,而不是寻求临时解决方案。模式匹配允许简洁地表达对象所需的“形状”(模式),并允许各种语句和表达式根据其输入测试该“形状”(匹配)。从 Haskell 到 C#,许多语言都因其简洁性和安全性而采用了模式匹配。

描述

模式是 (1)谓词_或测试(可应用于目标)和 (2) 一组局部变量(称为_模式__变量)的组合,仅当谓词成功应用时才从目标中提取到它。

类型_模式_由指定类型的谓词以及单个模式变量组成。

运算instanceof符 ( JLS 15.20.2 ) 被扩展为采用类型模式而不仅仅是类型。

这使我们能够将上面繁琐的代码重构为以下内容:

if (obj instanceof String s) {
// Let pattern matching do the work!
...
}

(在此代码中,该短语String s是类型模式。)其含义很直观。该instanceof运算符将目标obj与类型模式匹配,如下所示:如果obj是 的实例String,则将其强制转换为String并将值分配给变量s

模式匹配的条件性——如果一个值与模式不匹配,则不会为模式变量赋值——意味着我们必须仔细考虑模式变量的范围。我们可以做一些简单的事情,并说模式变量的范围是包含语句以及封闭块中的所有后续语句。但这会带来不幸的_中毒_后果,例如:

if (a instanceof Point p) {
...
}
if (b instanceof Point p) { // ERROR - p is in scope
...
}

换句话说,通过第二条语句,模式变量p将处于中毒状态 - 它在范围内,但它不应该被访问,因为它可能没有被分配值。但即使它不应该被访问,因为它在范围内,我们也不能只是再次声明它。这意味着模式变量在声明后可能会中毒,因此程序员必须为其模式变量考虑许多不同的名称。

模式变量不是使用模式变量范围的粗略近似,而是使用_流范围_的概念。模式变量仅位于编译器可以推断模式明确匹配并且变量将被分配值的范围内。该分析是流敏感的,其工作方式与现有的流分析(例如明确分配)类似。回到我们的例子:

if (a instanceof Point p) {
// p is in scope
...
}
// p not in scope here
if (b instanceof Point p) { // Sure!
...

}

座右铭是:“模式变量位于它明确匹配的范围内”。这允许安全地重用模式变量,并且既直观又熟悉,因为 Java 开发人员已经习惯了流程敏感分析。

当语句的条件表达式if变得比单个条件表达式更复杂时instanceof,模式变量的范围也会相应地增长。例如,在这段代码中:

if (obj instanceof String s && s.length() > 5) {
flag = s.contains("jdk");
}

模式变量s位于运算符右侧的范围内&&,以及 true 块中。 (&&仅当模式匹配成功并将值赋给 时才会计算运算符的右侧s。)另一方面,以下代码无法编译:

if (obj instanceof String s || s.length() > 5) {    // Error!
...
}

由于运算符的语义,可能尚未分配||模式变量,因此流分析表明该变量不在运算符右侧的范围内。s``s``||

使用模式匹配instanceof应该会显着减少 Java 程序中显式强制转换的总数。在编写相等方法时,类型测试模式特别有用。考虑以下取自《Effective Java》第 10 条的相等方法:

public final boolean equals(Object o) {
return (o instanceof CaseInsensitiveString) &&
((CaseInsensitiveString) o).s.equalsIgnoreCase(s);
}

使用类型模式意味着可以将其重写为更清晰的形式:

public final boolean equals(Object o) {
return (o instanceof CaseInsensitiveString cis) &&
cis.s.equalsIgnoreCase(s);
}

其他equals方法甚至得到了更显着的改进。考虑Point上面的类,我们可以在其中编写一个equals方法,如下所示:

public final boolean equals(Object o) {
if (!(o instanceof Point))
return false;
Point other = (Point) o;
return x == other.x
&& y == other.y;
}

使用模式匹配,我们可以将这些多个语句组合成一个表达式,消除重复并简化控制流程:

public final boolean equals(Object o) {
return (o instanceof Point other)
&& x == other.x
&& y == other.y;
}

模式变量的流程范围分析对于语句是否可以正常完成的概念很敏感。例如,考虑以下方法:

public void onlyForStrings(Object o) throws MyException {
if (!(o instanceof String s))
throw new MyException();
// s is in scope
System.out.println(s);
...
}

该方法测试其参数是否o为 a String,如果不是则抛出异常。只有println当条件语句正常完成时才可能到达该语句。由于条件语句中包含的语句永远无法正常完成,因此只有当条件表达式计算为值 时才会发生这种情况false,这意味着模式匹配已成功。因此,模式变量的范围s安全地包括方法块中条件语句后面的语句。

模式变量只是局部变量的特例,除了其范围的定义之外,在所有其他方面,模式变量都被视为局部变量。特别是,这意味着 (1) 它们可以被分配给,并且 (2) 它们可以隐藏字段声明。例如:

class Example1 {
String s;

void test1(Object o) {
if (o instanceof String s) {
System.out.println(s); // Field s is shadowed
s = s + "\n"; // Assignment to pattern variable
...
}
System.out.println(s); // Refers to field s
...
}
}

然而,模式变量的流作用域性质意味着必须小心确定名称是指隐藏字段声明的模式变量声明还是字段声明本身。

class Example2 {
Point p;

void test2(Object o) {
if (o instanceof Point p) {
// p refers to the pattern variable
...
} else {
// p refers to the field
...
}
}
}

语法相应instanceof 扩展:

RelationalExpression :
     ...
     RelationalExpression instanceof 参考类型
     RelationalExpression instanceof 模式

模式
     参考类型 标识符

未来的工作

未来的 JEP 将通过更丰富的模式形式来增强 Java 编程语言,例如记录类的解构模式,以及其他语言构造(例如switch表达式和语句)的模式匹配。

备择方案

类型模式的好处可以通过语句中的_流类型_if或_类型开关_构造来获得。模式匹配概括了这两种结构。