Java 14的資料記錄將如何改變編碼方式:減少或消除對Lombok依賴 - oracle

banq發表於2020-02-01

在本文中將介紹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最終版本之前,您不應依賴它。正如我在本文中所討論的那樣,當談論可能的未來功能時,應始終理解,僅出於探索目的而討論該功能。

以上為摘錄,完整文章點選標題見原文

相關文章