Ohhnews

分类导航

$ cd ..
InfoQ Java原文

Java 探索承载类以扩展 Records 之外的面向数据编程

#java#openjdk#面向数据编程#records#承载类

OpenJDK Amber 项目发布了一份新的设计说明,题为《Java 面向数据编程:超越记录》,概述了一种探索性的方法,旨在将类似记录的功能扩展到更灵活的类设计中。该文档介绍了“载体类”和“载体接口”的概念,旨在推广记录的核心优势,而无需施加严格的表示规则。

Java 16 中引入的记录提供了一种简洁的方法来建模不可变的数据载体。例如,一个记录声明如下:

$ java
record Point(int x, int y) { }

它会自动定义一个规范构造器、访问器方法以及 equalshashCodetoString 的实现。记录还参与解构模式,以便与 instanceofswitch 一起使用。结合密封类和模式匹配,记录支持在 Java 中建模代数数据类型。例如,HTTP 客户端或网关可以按如下方式表示不同的响应类型:

$ java
public sealed interface HttpResponse permits HttpResponse.Success, HttpResponse.NotFound, HttpResponse.ServerError {
    record Success(int status, String body) implements HttpResponse {}
    record NotFound(String message) implements HttpResponse {}
    record ServerError(int status, String error) implements HttpResponse {}
}

然后可以使用穷尽模式匹配来处理此类响应层次结构:

$ java
static String handle(HttpResponse response) {
   return switch (response) {
       case Success(var code, var body) -> "OK (" + code + "): " + body;
       case NotFound(var msg) -> "404: " + msg;
       case ServerError(var code, var err) -> "Error (" + code + "): " + err;
   };
}

在此示例中,编译器确保覆盖所有允许的响应类型。如果引入了新的响应变体,则必须更新 switch 表达式以降低错误处理不完整的风险。

在最近的一次讨论中,Oracle Java 语言架构师 Brian Goetz 指出,这种组合能够实现强大的数据建模,但其采用通常受到长期存在的面向对象设计习惯的限制。他观察到,开发者继续设计用于中介数据访问的 API,即使现代语言特性允许移除大部分这种间接性。

该设计说明重点关注无法使用记录的情况。许多现实世界的类型需要派生值或缓存值、替代的内部表示、可变性或继承。在这些情况下,开发者必须回退到传统的类并重新引入样板代码。该文档将这种转变描述为“掉下悬崖”;即稍微偏离参考模型就会导致代码量显著增加。

提议引入载体类是为了平滑这一过渡。载体类以类似于记录头的状态描述开始,但在其他方面表现为普通类:

$ java
class Point(int x, int y) {
    private final component int x;
    private final component int y;
}

状态描述定义了类的逻辑组件。编译器可以从这些组件派生出访问器、对象方法和解构模式。与记录不同,载体类不要求仅在这些组件中存储其状态。

这种灵活性使得难以用记录表达的模式成为可能,例如缓存派生值:

$ java
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 是在构造期间计算的,但不是状态描述的一部分。该类仍然可以受益于基于其组件的编译器生成的方法。

载体类也被设计为与模式匹配集成:

$ java
if (obj instanceof Point(var x, var y)) {
    // use x and y
}

该设计说明进一步讨论了与未来重构功能的兼容性,例如提议的 JEP 468,目前正在针对记录的表达式进行探索。

除了类之外,该提议还引入了载体接口。接口可以声明状态描述并参与跨实现的模式匹配:

$ java
interface Pair<T, U>(T first, U second) { }

switch (pair) {
    case Pair(var a, var b) -> ...
}

这种方法可以在保持强类型的同时,简化常见的类似元组的抽象。

该设计说明将载体类置于 Java 向面向数据编程更广泛转变的背景中。通过结合记录、密封类型、模式匹配以及潜在的载体类,该语言越来越鼓励开发者直接建模数据结构,而不是依赖重层级的 API。Goetz 认为,一个关键挑战是帮助开发者认识到,当将数据视为主要抽象时,可以消除多少支持代码。

目前,《超越记录》是一份探索性文档。尚未宣布具体的语法、JEP 或发布时间表。然而,这标志着 Amber 项目在减少样板代码和将现代语言特性扩展到更复杂的类设计方面的持续工作,这可能会影响 Java 开发者在未来版本中构建以数据为中心的 API 的方式。

关于作者

A N M Bazlur Rahman