派生记录创建(预览)
摘要
本文档描述了一项增强Java语言的提案,旨在为记录类引入派生创建功能。该特性允许开发者基于现有记录便捷地创建新记录,仅需指定发生变化的组件,从而简化代码。这是一个预览语言功能。
目标
- 提供一种简洁的方式,从现有记录值派生新的记录值。
- 通过消除为记录类显式声明“wither”方法(不可变对象的setter方法类似物)的需要,来简化记录类的声明。
动机
记录类是浅层不可变的数据类,其不可变性确保了安全性和可预测性。然而,要模拟状态演化(即基于旧记录创建新记录)时,过程可能相当繁琐。例如,仅想修改一个字段,也需要提取并传递所有字段:
Point newLoc = new Point(oldLoc.x() * 2, oldLoc.y(), oldLoc.z());
为了解决此问题,开发者通常会在记录类中手动编写wither方法:
record Point(int x, int y, int z) {
Point withX(int newX) { return new Point(newX, y, z); }
// ... withY, withZ 方法
}
然后便可以:
Point newLoc = oldLoc.withX(oldLoc.x() * 2);
但wither方法有两个问题:
- 增加了样板代码,违背了记录类旨在减少样板的初衷。
- 难以维护跨组件的语义约束。例如,若记录类包含两个长度必须相等的
List组件,单独更新其中一个的wither方法就可能破坏约束。
因此,需要一种更好的机制,让开发者专注于转换组件,而由编译器自动处理记录的解构和实例化。
描述
语法
派生记录创建表达式的形式为:
e with { ... }
左侧的表达式e是源记录值。右侧的代码块是一个转换块,用于修改源记录的状态。
工作原理
- 转换块内隐式地为源记录的每个组件声明了一个同名、同类型的局部变量,并初始化为该组件的值。
- 开发者可以在转换块中查询或修改这些局部变量的值。
- 转换块执行完毕后,会自动调用记录的规范构造函数,传入(可能已修改的)局部变量,来创建新的记录实例。
示例
Point oldLoc = new Point(1, 2, 3);
// 仅修改x组件
Point newLoc = oldLoc with { x = 0; }; // 等效于 new Point(0, 2, 3)
// 所有组件都进行修改(缩放)
Point scaledLoc = newLoc with {
x *= 2;
y *= 2;
z *= 2;
};
主要特性
- 简洁性:只需提及需要变更的组件,未提及的组件将自动沿用源记录的值。
- 安全性与约束:由于最终调用规范构造函数,因此所有跨组件的约束(在规范构造函数中定义)都会自动得到强制执行。
record Rational(int num, int denom) {
Rational { if (denom == 0) throw ...; }
}
Rational r = new Rational(3, 1);
Rational s = r with { denom = 0; }; // 将抛出IllegalArgumentException
- 可链式调用:支持链式操作以实现分步修改。
Point finalLoc = oldLoc
with { x *= 2; }
with { y *= 2; }
with { z *= 2; };
- 支持嵌套记录:可以嵌套使用
with表达式来修改嵌套的记录组件。record Marker(Point loc, String label, Icon icon) { }
Marker m = ...;
Marker scaled = m with { loc = loc with { x *= 2; y *= 2; z *= 2; }};
- 用于记录类自身方法:可在记录类内部使用以简化操作方法。
record Complex(double re, double im) {
Complex conjugate() { return this with { im = -im; }; }
}
语义与限制
- 转换块是正常的代码块,但不能包含
return、yield、break或continue语句(其目标包含此表达式)。
- 对隐式声明的组件变量进行赋值时,左侧必须是组件变量名或块内显式声明的局部变量名。
- 派生记录创建表达式不是语句表达式,不能单独作为语句使用。
使用说明
这是一个预览语言功能,在JDK 23中默认禁用。要使用,需在编译和运行时启用预览特性:
javac --release 23 --enable-preview Main.java
java --enable-preview Main
总结
派生记录创建特性通过引入with表达式,为从现有记录创建新记录提供了一种简洁、安全且符合人体工程学的语法。它消除了手动编写wither方法的样板,自动维护不可变性语义,并通过调用规范构造函数来保证数据约束,极大地提升了使用记录类进行函数式风格编程的便利性。