JEP 432:记录模式(第二次预览)
概括
_使用记录模式_增强 Java 编程语言以解构记录值。记录模式和类型模式可以嵌套,以实现强大的、声明性的、可组合形式的数据导航和处理。这是预览语言功能。
历史
记录模式由JEP 405提议作为预览功能,并在JDK 19中提供。此 JEP 提出了第二个预览版,并根据持续的经验和反馈进行了进一步的改进。
自第一次预览以来的主要变化是:
- 添加对通用记录模式的类型参数推断的支持,
- 添加对记录模式的支持以显示在增强语句的标题中
for
,并且 - 删除对命名记录模式的支持。
目标
-
扩展模式匹配以表达更复杂、可组合的数据查询。
-
不要更改类型模式的语法或语义。
动机
在 JDK 16 中,JEP 394扩展了该instanceof
运算符以采用_类型模式_并执行_模式匹配_。这个适度的扩展允许简化熟悉的 instanceof-and-cast 习惯用法:
// Old code
if (obj instanceof String) {
String s = (String)obj;
... use s ...
}
// New code
if (obj instanceof String s) {
... use s ...
}
在新代码中,如果在运行时 的值是 的实例,则obj
匹配类型模式。如果模式匹配,则表达式为,并且模式变量被初始化为强制转换为的值,然后可以在包含的块中使用该值。String s``obj``String``instanceof``true``s``obj``String
switch
在 JDK 17、JDK 18 和 JDK 19 中,我们还通过JEP 406、JEP 420和JEP 427将类型模式的使用扩展到大小写标签。
字体模式一次性消除了许多铸造的情况。然而,它们只是迈向更具声明性、以数据为中心的编程风格的第一步。由于 Java 支持新的、更具表现力的数据建模方式,模式匹配可以让开发人员表达其模型的语义意图,从而简化此类数据的使用。
模式匹配和记录类
记录类(JEP 395)是数据的透明载体。接收记录类实例的代码通常会提取数据,称为_组件_。例如,我们可以使用类型模式来测试某个值是否是记录类的实例Point
,如果是,则从该值中提取x
和组件:y
record Point(int x, int y) {}
static void printSum(Object obj) {
if (obj instanceof Point p) {
int x = p.x();
int y = p.y();
System.out.println(x+y);
}
}
此处的模式变量p
仅用于调用访问 器方法x()
和,该方法返回组件和y()
的值。 (在每个记录类中,其访问器方法与其组件之间都存在一一对应关系。)如果该模式不仅可以测试某个值是否是 的实例,而且还可以从 中提取和组件,那就更好了直接值,代表我们调用访问器方法。换句话说:x``y``Point``x``y
record Point(int x, int y) {}
void printSum(Object obj) {
if (obj instanceof Point(int x, int y)) {
System.out.println(x+y);
}
}
Point(int x, int y)
是一个_记录模式_。它将提取组件的局部变量声明提升到模式本身中,并在值与模式匹配时通过调用访问器方法来初始化这些变量。实际上,记录模式将记录的实例分解为其组件。
模式匹配的真正力量在于它可以优雅地扩展以匹配更复杂的对象图。例如,考虑以下声明:
record Point(int x, int y) {}
enum Color { RED, GREEN, BLUE }
record ColoredPoint(Point p, Color c) {}
record Rectangle(ColoredPoint upperLeft, ColoredPoint lowerRight) {}
我们已经看到,我们可以使用记录模式提取对象的组成部分。如果我们想从左上角提 取颜色,我们可以这样写:
static void printUpperLeftColoredPoint(Rectangle r) {
if (r instanceof Rectangle(ColoredPoint ul, ColoredPoint lr)) {
System.out.println(ul.c());
}
}
但我们ColoredPoint
本身就是一个记录,我们可能想进一步分解它。因此,记录模式支持_嵌套_,这允许记录组件进一步匹配嵌套模式并由嵌套模式分解。我们可以在记录模式中嵌套另一个模式,并同时分解外部和内部记录:
static void printColorOfUpperLeftPoint(Rectangle r) {
if (r instanceof Rectangle(ColoredPoint(Point p, Color c),
ColoredPoint lr)) {
System.out.println(c);
}
}
嵌套模式进一步使我们能够使用与将其组合在一起的代码一样清晰简洁的代码来分解聚合。例如,如果我们要创建一个矩形,我们可能会将构造函数嵌套在单个表达式中:
Rectangle r = new Rectangle(new ColoredPoint(new Point(x1, y1), c1),
new ColoredPoint(new Point(x2, y2), c2));
通过嵌套模式,我们可以使用与嵌套构造函数的结构相呼应的代码来解构这样的矩形:
static void printXCoordOfUpperLeftPointWithPatterns(Rectangle r) {
if (r instanceof Rectangle(ColoredPoint(Point(var x, var y), var c),
var lr)) {
System.out.println("Upper-left corner: " + x);
}
}
当然,嵌套模式可能无法匹配:
record Pair(Object x, Object y) {}
Pair p = new Pair(42, 42);
if (p instanceof Pair(String s, String t)) {
System.out.println(s + ", " + t);
} else {
System.out.println("Not a pair of strings");
}
这里的记录模式Pair(String s, String t)
包含两个嵌套模式,即String s
和String t
。Pair(String s, String t)
如果一个值是 a ,则它与模式匹配Pair
,并且递归地,其分量值与模式String s
和匹配String t
。在上面的示例代码中,这些递归模式匹配失败,因为两个记录组件值都不是字符串,因此else
块被执行。
总之,嵌套模式消除了导航对象的意外复杂性,以便我们可以专注于这些对象所表达的数据。
表达式instanceof
andswitch
并不是唯一方便记录模式分解行为的地方。在增强语句中允许记录模式for
可以轻松地循环记录值的集合并快速提取每个记录的组成部分。例如:
record Point(int x, int y) {}
static void dump(Point[] pointArray) {
for (Point(var x, var y) : pointArray) { // Record Pattern in header!
System.out.println("(" + x + ", " + y + ")");
}
}
含义很直观:在循环的每次迭代中,数组的每个连续元素或都Iterable
与标头中的记录模式进行模式匹配。
增强语句中的记录模式for
可以有嵌套模式,例如:
enum Color { RED, GREEN, BLUE }
record ColoredPoint(Point p, Color c) {}
record Rectangle(ColoredPoint upperLeft, ColoredPoint lowerRight) {}
static void printUpperLeftColors(Rectangle[] r) {
for (Rectangle(ColoredPoint(Point p, Color c), ColoredPoint lr): r) {
System.out.println(c);
}
}
描述
我们使用可嵌套记录模式扩展了 Java 编程语言。
模式的语法将变为:
Pattern:
TypePattern
ParenthesizedPattern
RecordPattern
TypePattern:
LocalVariableDeclaration
ParenthesizedPattern:
( Pattern )
RecordPattern:
ReferenceType RecordStructurePattern
RecordStructurePattern:
( [ RecordComponentPatternList ] )
RecordComponentPatternList :
Pattern { , Pattern }
记录模式
记录_模式_由一个类型、一个(可能为空)记录组件模式列表组成,该列表用于与相应的记录组件进行匹配。
例如,给定声明
record Point(int i, int j) {}
如果值是记录类型的实例,则v
它与记录模式匹配;如果是,则用调用value 上对应的访问器方法的结果来初始化模式变量,并将模式变量初始化为调用value 上对应的访问器方法的结果。 (模式变量的名称不需要与记录组件的名称相同;即,除了模式变量和被初始化之外,记录模式的行为相同。)Point(int i, int j)``Point``i``i``v``j``j``v``Point(int x, int y)``x``y
该null
值与任何记录模式都不匹配。
记录模式可用于var
匹配记录组件,而无需说明组件的类型。在这种情况下,编译器会推断模式引入的模式变量的类型var
。例如,模式Point(var a, var b)
是模式的简写Point(int a, int b)
。
由记录模式声明的模式变量集包括在记录组件模式列表中声明的所有模式变量。
如果表达式可以转换为模式中的记录类型而不需要未经检查的转换,则该表达式与记录模式兼容。
如果记录类是通用的,那么它可以在记录模式中用作参数化类型或原始类型。例如:
record Box<T>(T t) {}
static void test1(Box<String> bo) {
if (bo instanceof Box<String>(var s)) {
System.out.println("String " + s);
}
}
这里记录模式中的记录类类型是参数化类型。它可以等效地写成如下,在这种情况下推断类型参数:
static void test2(Box<String> bo) {
if (bo instanceof Box(var s)) { // Inferred to be Box<String>(var s)
System.out.println("String " + s);
}
}
推理适用于嵌套记录模式。例如:
static void test3(Box<Box<String>> bo) {
if (bo instanceof Box<Box<String>>(Box(var s))) {
System.out.println("String " + s);
}
}