Java 記錄類Record詳解
引言
在傳統(tǒng) Java 開發(fā)中,創(chuàng)建一個純粹用于封裝數(shù)據(jù)的類(如 DTO 或值對象)往往需要編寫大量樣板代碼,包括構(gòu)造器、getter、equals、hashCode 和 toString 方法。這些代碼雖然重復(fù),卻難以避免,不僅影響開發(fā)效率,也降低了代碼可讀性。
為了解決這一問題,Java 在 JEP 359 中提出了“記錄類”這一語言特性,并于 Java 14 首次以預(yù)覽形式引入,在 Java 16 中正式發(fā)布。記錄類的核心設(shè)計目標是為數(shù)據(jù)攜帶類提供一種簡潔、可讀性強且類型安全的聲明方式。
本篇文章將帶你深入理解記錄類的原理與使用,掌握其各種高級特性,并通過豐富示例、性能對比和最佳實踐指導(dǎo),助你在實際項目中合理使用記錄類,寫出更加優(yōu)雅、現(xiàn)代化的 Java 代碼。
語法與基本用法
記錄類的定義方式非常簡單,使用 record 關(guān)鍵字聲明,取代了傳統(tǒng)類的冗長結(jié)構(gòu)?;菊Z法如下:
public record Person(String name, int age) {
}上述代碼定義了一個 Person 記錄類,包含兩個組件(component):name 和 age,它們將被編譯器自動提升為私有 final 字段,并生成相應(yīng)的方法(稍后將詳細介紹)。
與傳統(tǒng)類相比,記錄類具備以下特性:
- 自動生成構(gòu)造器、訪問器、
equals、hashCode、toString。 - 所有字段默認
private final。 - 構(gòu)造器參數(shù)與字段一一對應(yīng)。
- 支持接口實現(xiàn)、泛型和靜態(tài)成員。
示例:基本記錄類定義
public record Book(String title, double price) {
}
public class Main {
public static void main(String[] args) {
Book book = new Book("Java 精通之路", 79.9);
System.out.println(book.title());
System.out.println(book);
}
}輸出結(jié)果:
Java 精通之路
Book[title=Java 精通之路, price=79.9]
自動生成方法詳解
記錄類的核心價值之一是其自動生成的標準方法,這不僅減少了開發(fā)者的重復(fù)工作,也保證了行為的一致性和語義清晰性。以下是記錄類在編譯階段自動生成的方法列表:
- 所有字段的 訪問器方法(accessor)
- 一個 規(guī)范構(gòu)造器(canonical constructor)
- equals(Object obj) 方法
- hashCode() 方法
- toString() 方法
1. 訪問器方法
每個組件都會生成一個訪問器方法,其方法名與字段名一致。例如:
public record User(String username, String role) {
}
User user = new User("alice", "admin");
System.out.println(user.username()); // 輸出:alice
System.out.println(user.role()); // 輸出:admin注意:記錄類不生成傳統(tǒng)的 getUsername() 形式的方法。
2. equals 和 hashCode 方法
記錄類會基于字段值自動生成合理的 equals 和 hashCode 方法。
User user1 = new User("alice", "admin");
User user2 = new User("alice", "admin");
System.out.println(user1.equals(user2)); // true
System.out.println(user1.hashCode() == user2.hashCode()); // true比較是基于組件值進行的,而非引用。
3. toString 方法
記錄類默認實現(xiàn)了符合邏輯的 toString() 方法,格式為:類名[字段1=值1, 字段2=值2]
System.out.println(user1.toString()); // 輸出:User[username=alice, role=admin]
4. 規(guī)范構(gòu)造器
編譯器會生成一個公共構(gòu)造器,其參數(shù)列表與組件順序一致:
public User(String username, String role) {
this.username = username;
this.role = role;
}此構(gòu)造器不能省略字段賦值,且不能繞過不可變性。
不可變性的特性與價值
記錄類的一個顯著特性是:不可變性(Immutability)。一旦創(chuàng)建了記錄類實例,其狀態(tài)便不可更改,這使得記錄類天然適合用于線程安全的數(shù)據(jù)傳遞、緩存鍵、日志記錄和函數(shù)式編程等場景。
不可變性的實現(xiàn)方式
在記錄類中:
- 所有組件字段默認是
private final,且不能被修改。 - 沒有生成
setter方法。 - 構(gòu)造函數(shù)中必須為所有字段賦值,且不能通過反射等方式繞過字段 final 的限制(除非使用非法手段)。
public record Customer(String id, String name) {
// 無法修改 id 和 name
}
Customer c1 = new Customer("001", "Alice");
c1.name = "Bob"; // 編譯錯誤,字段是 final 的不可變性的優(yōu)勢
- 線程安全:多個線程可安全共享記錄類實例,而無需加鎖。
- 更少的副作用:狀態(tài)不可更改,避免由于共享狀態(tài)帶來的錯誤。
- 更簡單的調(diào)試與測試:數(shù)據(jù)不會在調(diào)用鏈中被意外修改,提高可預(yù)測性。
- 易于緩存和哈希結(jié)構(gòu)使用:不可變對象可作為 Map 的鍵,不會影響哈希值。
與傳統(tǒng)類的對比
在傳統(tǒng) Java 類中,需要人為地將字段設(shè)置為 private final,并避免提供 setter 方法,才能勉強模擬不可變性:
public final class ImmutablePerson {
private final String name;
private final int age;
public ImmutablePerson(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
}記錄類通過語法層級的約束,天然支持不可變性,避免了人為失誤。
注意事項:深層不可變性
雖然記錄類本身是不可變的,但如果其字段是可變對象(如 List、Map、數(shù)組),則需要謹慎處理。
public record UserProfile(String username, List<String> tags) {
}
List<String> list = new ArrayList<>();
list.add("java");
UserProfile profile = new UserProfile("bob", list);
profile.tags().add("record"); // 可變字段被修改上述代碼中,雖然 tags 字段本身是 final,但它引用的是一個可變列表,因此對象并不是真正不可變。
解決方案:
- 在構(gòu)造函數(shù)中進行 defensive copy(防御性復(fù)制)。
- 使用
List.copyOf()創(chuàng)建不可變集合。
public record SafeUserProfile(String username, List<String> tags) {
public SafeUserProfile {
tags = List.copyOf(tags);
}
}這樣就能保證 tags 無法在外部被修改,真正實現(xiàn)深層不可變。
構(gòu)造器機制
盡管記錄類自動為我們生成了標準構(gòu)造器,但在實際開發(fā)中,仍有很多場景需要我們自定義構(gòu)造邏輯,比如字段校驗、數(shù)據(jù)轉(zhuǎn)換等。Java 記錄類支持三種構(gòu)造器形式:
- 規(guī)范構(gòu)造器(Canonical Constructor)
- 緊湊構(gòu)造器(Compact Constructor)
- 自定義構(gòu)造器(Custom Constructor)
規(guī)范構(gòu)造器(Canonical Constructor)
規(guī)范構(gòu)造器是由編譯器自動生成的構(gòu)造函數(shù),其參數(shù)列表與組件順序一致,默認行為是將所有字段賦值:
public record Product(String name, double price) {
// 編譯器生成如下構(gòu)造器:
// public Product(String name, double price) {
// this.name = name;
// this.price = price;
// }
}我們也可以顯式地定義規(guī)范構(gòu)造器,來加入自定義邏輯,例如參數(shù)校驗:
public record Product(String name, double price) {
public Product {
if (price < 0) {
throw new IllegalArgumentException("價格不能為負數(shù)");
}
}
}緊湊構(gòu)造器(Compact Constructor)
Java 為記錄類提供了一種語法糖,稱為“緊湊構(gòu)造器”。在緊湊構(gòu)造器中,我們無需列出參數(shù)列表,編譯器會自動將構(gòu)造器參數(shù)與字段綁定。
public record Student(String name, int age) {
public Student {
if (age < 0) {
throw new IllegalArgumentException("年齡不能為負數(shù)");
}
}
}等價于:
public record Student(String name, int age) {
public Student(String name, int age) {
if (age < 0) {
throw new IllegalArgumentException("年齡不能為負數(shù)");
}
this.name = name;
this.age = age;
}
}緊湊構(gòu)造器簡化了字段賦值過程,適用于多數(shù)校驗邏輯場景。
自定義構(gòu)造器與重載
記錄類可以定義額外的構(gòu)造器,但這些構(gòu)造器必須調(diào)用規(guī)范構(gòu)造器,不能繞過字段賦值流程:
public record Coordinate(int x, int y) {
public Coordinate() {
this(0, 0); // 默認構(gòu)造
}
public Coordinate(int value) {
this(value, value); // 重載構(gòu)造
}
}注意:
- 所有非規(guī)范構(gòu)造器都必須顯式調(diào)用
this(...)。 - 不能定義不初始化所有字段的構(gòu)造器。
通過構(gòu)造器機制,記錄類不僅保持了不可變性,還為開發(fā)者提供了足夠的靈活性,用于字段校驗、默認值填充、工廠構(gòu)造等實際需求。
高級特性
記錄類雖然語法簡潔,但功能并不簡單。它支持許多高級語言特性,包括靜態(tài)成員、接口實現(xiàn)、泛型定義以及本地記錄類的聲明,使其具備在復(fù)雜應(yīng)用中廣泛使用的能力。
靜態(tài)成員
記錄類可以像普通類一樣包含靜態(tài)字段、靜態(tài)方法和靜態(tài)代碼塊,這些靜態(tài)成員的行為與傳統(tǒng)類完全一致。
public record Color(int red, int green, int blue) {
public static final Color BLACK = new Color(0, 0, 0);
public static final Color WHITE = new Color(255, 255, 255);
public static String toHex(Color c) {
return String.format("#%02x%02x%02x", c.red(), c.green(), c.blue());
}
}
Color white = Color.WHITE;
System.out.println(Color.toHex(white)); // 輸出:#ffffff接口實現(xiàn)
記錄類可以實現(xiàn)接口(包括函數(shù)式接口),并提供對應(yīng)方法實現(xiàn)。
public interface Identifiable {
String id();
}
public record Employee(String id, String name) implements Identifiable {
// 自動實現(xiàn) id() 方法
}記錄類實現(xiàn)接口時,可以通過組件直接滿足接口方法簽名,進一步提升了類型的表達力。
泛型支持
記錄類可以定義為泛型類型,從而適用于多種數(shù)據(jù)類型的封裝。
public record Pair<K, V>(K key, V value) {
}
Pair<String, Integer> entry = new Pair<>("age", 30);
System.out.println(entry.key()); // 輸出:age
System.out.println(entry.value()); // 輸出:30泛型記錄類尤其適用于通用值對象、鍵值對封裝、元組等使用場景。
本地記錄類(Local Records)
從 Java 16 開始,記錄類也可以在方法內(nèi)部定義,稱為“本地記錄類”,適用于封裝局部方法邏輯中的中間數(shù)據(jù)結(jié)構(gòu)。
public class ReportGenerator {
public void generate() {
record Summary(String title, int count) {}
Summary summary = new Summary("周報", 12);
System.out.println(summary);
}
}本地記錄類只在方法范圍內(nèi)可見,能夠提升臨時數(shù)據(jù)處理的類型安全性與可讀性,避免引入冗余的外部類定義。
通過這些高級特性,記錄類在保持簡潔的同時,依然具備高度靈活性,能夠應(yīng)對多種開發(fā)需求,適用于從簡單 DTO 到泛型模型、工具類的廣泛場景。
限制與注意事項
盡管記錄類極大簡化了不可變數(shù)據(jù)類的開發(fā),但其設(shè)計也存在一些固有的限制,了解這些限制有助于合理使用,避免不合適的場景導(dǎo)致代碼設(shè)計問題。
1. 繼承限制
- 記錄類隱式繼承自
java.lang.Record,不允許繼承其他類。 - 記錄類本身是
final的,不能被繼承。 - 這意味著無法通過繼承擴展記錄類行為,只能通過組合或接口實現(xiàn)進行擴展。
// 編譯錯誤,記錄類不能繼承其他類 public record MyRecord extends SomeClass { } // 編譯錯誤,記錄類不能被繼承 public class SubRecord extends MyRecord { }2. 字段限制
- 所有組件字段自動為
private final,不能定義非final字段。 - 不支持額外的實例字段,只能通過靜態(tài)字段擴展類功能。
3. 不支持無參構(gòu)造器
- 記錄類必須初始化所有組件字段,不能定義無參構(gòu)造器。
- 但可通過自定義構(gòu)造器為字段賦默認值,實現(xiàn)類似無參構(gòu)造器的效果。
4. 不支持可變字段
- 記錄類設(shè)計為不可變,因此組件字段不能是可變的。
- 如果字段引用了可變對象,需自行保證深不可變性(如使用不可變集合或防御性復(fù)制)。
5. 限制擴展性
- 由于不能繼承和添加實例字段,記錄類不適合表示需要復(fù)雜行為和狀態(tài)管理的實體。
- 更適合用于簡單數(shù)據(jù)載體(如 DTO、值對象)。
6. 序列化限制
- 記錄類實現(xiàn)了
Serializable,但序列化時遵循組件字段的序列化規(guī)則。 - 如果字段不可序列化,會導(dǎo)致序列化失敗。
理解并遵守這些限制,有助于避免誤用記錄類導(dǎo)致設(shè)計不佳和維護困難。
到此這篇關(guān)于Java 記錄類Record詳解的文章就介紹到這了,更多相關(guān)Java Record 類內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Spring Boot 與 Apache Pulsar 集成構(gòu)建高性能
本文介紹了ApachePulsar作為新一代消息中間件的特點,并詳細說明了如何在SpringBoot應(yīng)用Pulsar,還介紹了Pulsar的高級特性和實踐應(yīng)用案例,如訂單處理系統(tǒng)和實時數(shù)據(jù)分析,感興趣的朋友跟隨小編一起看看吧2026-04-04
基于Java 數(shù)組內(nèi)存分配的相關(guān)問題
本篇文章是對Java中數(shù)組內(nèi)存分配進行了詳細的分析介紹,需要的朋友參考下2013-05-05

