Java 14的数据记录将如何改变编码方式:减少或消除对Lombok依赖 - oracle


在本文中将介绍Java中记录的概念。记录Record是Java类的一种新形式,旨在

  • 提供对数据聚合建模的一流方法
  • 弥补Java类型系统中的可能差距
  • 提供通用编程模式的语言级语法
  • 减少类样板

什么是Java记录?
关于Java的最常见的抱怨之一是您需要编写很多代码才能使一个类有用。通常,您需要编写以下内容:
  • toString()
  • hashCode() 和 equals()
  • Getter 方法
  • 公开构造器

对于简单的业务领域类,这些方法通常很无聊,重复,并且很容易机械生成(并且IDE通常提供此功能),但是到目前为止,语言本身还没有提供任何方法来实现此目的。
当您阅读别人的代码时,这种令人沮丧的差距实际上更加严重。例如,看起来作者使用的是IDE生成的,hashCode()并且equals()可以处理该类的所有字段,但是如何确定不检查实现的每一行呢?如果在重构过程中添加了字段并且未重新生成方法,会发生什么?
记录的目的是扩展Java语言语法,并创建一种方式来表示类是“字段,只是字段,除了字段之外什么都没有。”通过对类进行声明,编译器可以通过以下方式提供帮助:自动创建所有方法诸如hashCode(),在这些方法中使所有字段有参与。

传统Java类如下:

public final class FXOrderClassic {
    private final int units;
    private final CurrencyPair pair;
    private final Side side;
    private final double price;
    private final LocalDateTime sentAt;
    private final int ttl;

    public FXOrderClassic(int units, 
               CurrencyPair pair, 
               Side side, 
               double price, 
               LocalDateTime sentAt, 
               int ttl) {
        this.units = units;
        this.pair = pair; // CurrencyPair is a simple enum
        this.side = side;
// Side is a simple enum
        this.price = price;
        this.sentAt = sentAt;
        this.ttl = ttl;
    }

    public int units() {
        return units;
    }

    public CurrencyPair pair() {
        return pair;
    }

    public Side side() {
        return side;
    }

    public double price() { return price; }

    public LocalDateTime sentAt() {
        return sentAt;
    }

    public int ttl() {
        return ttl;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) 
            return false;

        FXOrderClassic that = (FXOrderClassic) o;

        if (units != that.units) return false;
        if (Double.compare(that.price, price) != 0) 
            return false;
        if (ttl != that.ttl) return false;
        if (pair != that.pair) return false;
        if (side != that.side) return false;
        return sentAt != null
            sentAt.equals(that.sentAt) : that.sentAt == null;
    }

    @Override
    public int hashCode() {
        int result;
        long temp;
        result = units;
        result = 31 * result + 
                   (pair != null ? pair.hashCode() : 0);
        result = 31 * result + 
                   (side != null ? side.hashCode() : 0);
        temp = Double.doubleToLongBits(price);
        result = 31 * result + 
                   (int) (temp ^ (temp >>> 32));
        result = 31 * result + 
                   (sentAt != null ? sentAt.hashCode() : 0);
        result = 31 * result + ttl;
        return result;
    }

    @Override
    public String toString() {
        return
"FXOrderClassic{" +
               
"units=" + units +
               
", pair=" + pair +
               
", side=" + side +
               
", price=" + price +
               
", sentAt=" + sentAt +
               
", ttl=" + ttl +
                '}';
    }
}


提供了用于声明记录的简洁语法,其中程序员所需要做的就是声明组成记录的组件名称和类型,如下所示:

public record FXOrder(int units,
                      CurrencyPair pair,
                      Side side,
                      double price,
                      LocalDateTime sentAt,
                      int ttl) {}

FXOrder类型就是提供的最终状态,它的任何实例都是字段值的透明聚合。
要访问新的语言功能,您需要使用Preview标志编译任何声明记录的代码:

javac --enable-preview -source 14 FXOrder.java

如果使用javap来检查,可以看到编译器已自动生成了一堆样板代码。(在下面的反编译中,我仅显示方法及其签名。)


$ javap FXOrder.class
Compiled from "FXOrder.java"
public final class FXOrder extends java.lang.Record {
  public FXOrder(int, CurrencyPair, Side, double
      java.time.LocalDateTime, int);
  public java.lang.String toString();
  public final int hashCode();
  public final boolean equals(java.lang.Object);
  public int units();
  public CurrencyPair pair();
  public Side side();
  public double price();
  public java.time.LocalDateTime sentAt();
  public int ttl();
}

这看起来非常类似于之前基于类的实现的传统代码中的方法集。实际上,构造函数和访问器方法的行为均与以前完全相同。
记录是一种特殊的类形式,它以最小的语法实现了数据载体模式。您期望的所有样板代码都将由编译器自动生成。

紧凑的构造函数
您可能想验证订单以确保它们不尝试买卖负数量或设置无效的TTL值:

public record FXOrder(int units, 
                      CurrencyPair pair, 
                      Side side, 
                      double price, 
                      LocalDateTime sentAt, 
                      int ttl) {
    public FXOrder {
        if (units < 1) {
            throw new IllegalArgumentException(
                "FXOrder units must be positive");
        }
        if (ttl < 0) {
            throw new IllegalArgumentException(
               
"FXOrder TTL must be positive, or 0 for market orders");
        }
        if (price <= 0.0) {
            throw new IllegalArgumentException(
               
"FXOrder price must be positive");
        }
    }
}

紧凑的构造函数不会导致编译器生成单独的构造函数。而是,您在紧凑型构造函数中指定的代码将在规范构造函数的开头显示为额外的代码。您无需为字段指定构造函数参数的分配,该分配仍会自动生成并以通常的方式出现在构造函数中。
与其他语言中的匿名元组相比,Java记录具有的一个优势是,记录的构造函数主体允许在创建记录时运行代码。这样可以进行验证(如果通过了无效状态,则抛出异常)。在纯结构元组中这是不可能的。

总结
记录旨在成为简单的数据载体,这是元组的一种版本,以逻辑上一致的方式适合Java的已建立类型系统。这将帮助许多应用程序使域类更清晰,更小。它还将帮助团队消除对底层模式的许多手工编码实现,并减少或消除对Lombok之类的库的需求。
但是,就像密封类型一样,将来会出现一些最重要的记录用例。模式匹配,尤其是允许将记录分解成其各个组成部分的解构模式,显示了巨大的希望,并且很可能会改变许多开发人员使用Java进行编程的方式。密封类型和记录的组合还为Java提供了一种称为代数数据类型的语言功能。
如果您熟悉其他编程语言中的这些功能,那就太好了。如果没有,请不要担心。它们被设计为适合您已经知道的Java语言,并且易于在您的代码中开始使用。
但是,您必须始终记住,在交付包含特定语言功能的Java最终版本之前,您不应依赖它。正如我在本文中所讨论的那样,当谈论可能的未来功能时,应始终理解,仅出于探索目的而讨论该功能。

以上为摘录,完整文章点击标题见原文