OpenJDK的Amber项目发布了一篇新的设计说明文档,即《Java的数据导向编程:超越记录结构》,该文档提出了一种探索性方法,旨在将类似记录的结构特性应用到更为灵活的类设计中。这份文档介绍了“载体类”和“载体接口”的概念,这些概念的目的在于在不强加严格表示规则的前提下,让记录结构的核心优势得到广泛应用。
在Java 16中引入的记录结构,为建模不可变的数据载体提供了一种简洁的方式。例如这样的记录声明:
record Point(int x, int y) { }
这种声明会自动生成一个规范构造函数、访问器方法,以及equals、hashCode和toString方法的实现。记录结构还可以用于与instanceof和switch语句结合使用。结合密封类和模式匹配机制,记录结构能够帮助在Java中建模代数数据类型。例如,一个HTTP客户端或网关可以这样表示不同的响应类型:
public sealed interface HttpResponse permits HttpResponse.Success, HttpResponseNotFound, HttpResponse.ServerError {
record Success(int status, String body) implements HttpResponse {};
record NotFound(String message) implements HttpResponse {};
record ServerError(int status, String error) implements HttpResponse {};
}
对于这样的响应类型层次结构,可以使用穷举模式匹配来进行处理:
static String handle(HttpResponse response) {
return switch (response) {
case Success(var code, var body) -> "OK (" + code + "): " + body;
caseNotFound(var msg) -> "404: " + msg;
case ServerError(var code, var err) -> "Error (" + code + "): " + err;
};
}
在这个例子中,编译器会确保所有允许的响应类型都被覆盖到了。如果出现了新的响应类型,就必须更新switch语句中的匹配条件,以避免错误处理不完整的情况发生。
在最近的一次讨论中,Oracle公司的Java语言架构师Brian Goetz指出,这种技术组合确实能够实现强大的数据建模功能,但它的普及程度往往受到传统面向对象设计习惯的限制。他观察到,即使现代编程语言提供了很多可以消除间接操作的功能,开发者们仍然倾向于设计那些用于处理数据访问的API。
这篇设计说明文档重点讨论了那些不适合使用记录结构的情况。在许多现实世界中的数据类型中,需要派生值、缓存值、可变性的机制或继承关系,因此在这种情况下,开发者不得不重新使用传统的类结构,从而导致代码量增加。文档将这种转变描述为“走下悬崖”;也就是说,只要稍微偏离参考模型一点点,就会导致代码量显著增加。
为了解决这一过渡问题,人们提出了“载体类”这一概念。载体类的状态描述类似于记录的头部信息,但在其他方面则表现得像普通的类:
class Point(int x, int y) {
private final component int x;
private final component int y;
}
这种状态描述定义了该类的逻辑组成部分。通过这些组成部分,编译器可以生成相应的访问器方法、对象方法以及解构模式。与记录不同,载体类并不一定需要将其状态信息完全存储在这些组成部分中。
这种灵活性使得一些使用记录难以实现的机制成为可能,比如缓存派生出的值:
class Point(int x, int y) {
private final component int x;
private final component int y;
private final double norm;
Point { norm = Math.hypot(x, y); }
double norm() { return norm; }
}
在这里,norm这个值是在构造过程中计算出来的,但它并不属于类的状态描述部分。不过,该类仍然可以利用编译器根据其组成部分生成的相应方法。
载体类还被设计为能够与模式匹配机制结合使用:
if (obj instanceof Point(var x, var y)) {
// 可以使用x和y
}
设计说明中还提到了载体类与未来某些重构功能的兼容性,比如正在探索中的JEP 468项目,该项目旨在改进记录类型的表达能力。
除了类之外,该提案还引入了“载体接口”这一概念。接口也可以定义状态描述,并能够被用于不同实现之间的模式匹配:
interface Pair(T first, U second) { }
switch (pair) {
case Pair(var a, var b) -> ...
}
这种设计方式可以在保持强类型特性的同时,简化那些类似元组的抽象数据结构。
总体而言,载体类的出现体现了Java向面向数据编程方向发展的趋势。通过结合记录、密封类型、模式匹配以及载体类等技术,Java语言越来越鼓励开发者直接对数据结构进行建模,而不再依赖层次复杂的API。Goetz指出,一个关键挑战在于帮助开发者认识到:当将数据作为主要的抽象概念来处理时,许多辅助代码其实是可以省去的。
目前,“超越记录”这份提案还处于探索阶段。尚未公布具体的语法规范、JEP编号或发布时间表。不过,它确实表明Project Amber项目仍在继续推进相关工作,旨在减少冗余代码,并将现代语言特性应用到更复杂的类设计中。这些努力未来很可能会影响Java开发者构建以数据为中心的API的方式。
