JEP 305: Pattern Matching for instanceof (Preview) | instanceof 的模式匹配(预览版)
摘要
通过为 instanceof
运算符添加 模式匹配 来增强 Java 编程语言。模式匹配 允许在程序中表达常见逻辑,即条件地从对象中提取组件,使表达更简洁和安全。这是 JDK 14 中的一项 预览语言特性。
动机
几乎每个程序都包含某种逻辑,这种逻辑将检查表达式是否具有某种类型或结构,然后有条件地提取其状态组件以供进一步处理。例如,所有 Java 程序员都熟悉 instanceof-and-cast 的习惯用法:
if (obj instanceof String) {
String s = (String) obj;
// 使用 s
}
这里发生了三件事:一个测试(obj
是否为 String
?)、一个转换(将 obj
转换为 String
)以及一个新局部变量的声明(s
),以便我们可以使用字符串值。这种模式简单直接,所有 Java 程序员都能理解,但由于几个原因,它并不是最优的。它很繁琐;进行类型测试和转换是不必要的(在 instanceof
测试之后你还能做什么?)。这种样板代码——特别是类型 String
的三次出现——掩盖了后面更重要的逻辑。但最重要的是,这种重复为错误悄悄潜入程序提供了机会。
与其寻找临时解决方案,我们认为现在是 Java 采用 模式匹配 的时候了。模式匹配允许简洁地表达所需对象的“形状”(即 模式),并为各种语句和表达式提供将其输入与“形状”进行比较(即 匹配)的功能。从 Haskell 到 C#,许多语言都因其简洁性和安全性而采用了模式匹配。
描述
模式(Pattern)是(1)一个可应用于目标的 谓词(Predicate)和(2)一组 绑定变量(Binding Variables)的组合,这些绑定变量只有在谓词成功应用于目标时才会从目标中提取。
类型测试模式(Type Test Pattern)由一个指定类型的谓词和一个单一的绑定变量组成。
instanceof
运算符(JLS 15.20.2)被扩展为接受类型测试模式而不仅仅是类型。在下面的代码中,String s
是类型测试模式:
if (obj instanceof String s) {
// 在这里可以使用 s
} else {
// 在这里不能使用 s
}
instanceof
运算符将目标 obj
与类型测试模式进行“匹配”的过程如下:如果 obj
是 String
的一个实例,那么它将被转换为 String
并赋值给绑定变量 s
。绑定变量 s
在 if
语句的 true
分支中有效,而在 if
语句的 false
分支中无效。
与局部变量的作用域不同,绑定变量的作用域是由包含它的表达式和语句的语义决定的。例如,在以下代码中:
if (!(obj instanceof String s)) {
.. s.contains(..) .. // 这里不能使用 s,因为 s 尚未在作用域内
} else {
.. s.contains(..) .. // 在这里可以使用 s
}
(注意:在上面的代码示例中,s
在第一个条件块中实际上是不可用的,因为 s
是在 instanceof
表达式内部定义的,并且只有在该表达式为 true
时才在作用域内。这里为了解释作用域的概念而使用了注释。)
if
语句的 true
分支中的 s
指的是包围类中的一个字段,而 false
分支中的 s
指的是由 instanceof
运算符引入的绑定变量。
当 if
语句的条件变得比单个 instanceof
更复杂时,绑定变量的作用域也会相应地扩大。例如,在以下代码中:
if (obj instanceof String s && s.length() > 5) {.. s.contains(..) ..}
绑定变量 s
在 &&
操作符的右侧以及 true
分支中都在作用域内。(只有当 instanceof
成功并将 s
赋值后,右侧才会被评估。)另一方面,在以下代码中:
if (obj instanceof String s || s.length() > 5) {.. s.contains(..) ..}
绑定变量 s
在 ||
操作符的右侧以及 true
分支中都不在作用域内。(在这些点上,s
指的是包围类中的一个字段。)
当目标是 null
时,instanceof
的工作方式没有任何变化。也就是说,只有当 obj
不是 null
时,模式才会匹配,并且 s
才会被赋值。
在 instanceof
中使用模式匹配将大大减少 Java 程序中的显式类型转换数量。此外,类型测试模式在编写相等性方法时特别有用。考虑从 Effective Java 一书的第 10 项中提取的以下相等性方法:
@Override public boolean equals(Object o) {
return (o instanceof CaseInsensitiveString) &&
((CaseInsensitiveString) o).s.equalsIgnoreCase(s);
}
使用类型测试模式意味着它可以重写为更清晰的:
@Override public boolean equals(Object o) {
return (o instanceof CaseInsensitiveString cis) &&
cis.s.equalsIgnoreCase(s);
}
instanceof
的 语法 相应地进行了扩展:
RelationalExpression:
...
RelationalExpression `instanceof` ReferenceType
RelationalExpression `instanceof` Pattern
Pattern:
ReferenceType Identifier
未来工作
未来的 JEP(Java Enhancement Proposals)将使用模式匹配来增强 Java 编程语言的其他语言结构,如 switch
表达式和语句。
备选方案
类型测试模式的优势可以通过 if
语句中的 流类型(flow typing)或者通过 类型切换(type switch)构造来获得。但模式匹配概括了这两种构造。
依赖项
该实现可能会使用 JEP 309(动态类文件常量)。
摘要
Enhance the Java programming language with pattern matching for the instanceof
operator. Pattern matching allows common logic in a program, namely the conditional extraction of components from objects, to be expressed more concisely and safely. This is a preview language feature in JDK 14.
动机
Nearly every program includes some sort of logic that combines testing if an expression has a certain type or structure, and then conditionally extracting components of its state for further processing. For example, all Java programmers are familiar with the instanceof-and-cast idiom:
if (obj instanceof String) {
String s = (String) obj;
// use s
}
There are three things going on here: a test (is obj
a String
?), a conversion (casting obj
to String
), and the declaration of a new local variable (s
) so we can use the string value. This pattern is straightforward and understood by all Java programmers, but is suboptimal for several reasons. It is tedious; doing both the type test and cast should be unnecessary (what else would you do after an instanceof
test?). This boilerplate -- in particular, the three occurrences of the type String
--- obfuscates the more significant logic that follows. But most importantly, the repetition provides opportunities for errors to creep unnoticed into programs.
Rather than reach for ad-hoc solutions, we believe it is time for Java to embrace pattern matching. Pattern matching allows the desired 'shape' of an object to be expressed concisely (the pattern), and for various statements and expressions to test that 'shape' against their input (the matching). Many languages, from Haskell to C#, have embraced pattern matching for its brevity and safety.