Java 記錄類Record詳解
引言
在傳統(tǒng) Java 開(kāi)發(fā)中,創(chuàng)建一個(gè)純粹用于封裝數(shù)據(jù)的類(如 DTO 或值對(duì)象)往往需要編寫大量樣板代碼,包括構(gòu)造器、getter、equals、hashCode 和 toString 方法。這些代碼雖然重復(fù),卻難以避免,不僅影響開(kāi)發(fā)效率,也降低了代碼可讀性。
為了解決這一問(wèn)題,Java 在 JEP 359 中提出了“記錄類”這一語(yǔ)言特性,并于 Java 14 首次以預(yù)覽形式引入,在 Java 16 中正式發(fā)布。記錄類的核心設(shè)計(jì)目標(biāo)是為數(shù)據(jù)攜帶類提供一種簡(jiǎn)潔、可讀性強(qiáng)且類型安全的聲明方式。
本篇文章將帶你深入理解記錄類的原理與使用,掌握其各種高級(jí)特性,并通過(guò)豐富示例、性能對(duì)比和最佳實(shí)踐指導(dǎo),助你在實(shí)際項(xiàng)目中合理使用記錄類,寫出更加優(yōu)雅、現(xiàn)代化的 Java 代碼。
語(yǔ)法與基本用法
記錄類的定義方式非常簡(jiǎn)單,使用 record 關(guān)鍵字聲明,取代了傳統(tǒng)類的冗長(zhǎng)結(jié)構(gòu)?;菊Z(yǔ)法如下:
public record Person(String name, int age) {
}上述代碼定義了一個(gè) Person 記錄類,包含兩個(gè)組件(component):name 和 age,它們將被編譯器自動(dòng)提升為私有 final 字段,并生成相應(yīng)的方法(稍后將詳細(xì)介紹)。
與傳統(tǒng)類相比,記錄類具備以下特性:
- 自動(dòng)生成構(gòu)造器、訪問(wèn)器、
equals、hashCode、toString。 - 所有字段默認(rèn)
private final。 - 構(gòu)造器參數(shù)與字段一一對(duì)應(yīng)。
- 支持接口實(shí)現(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]
自動(dòng)生成方法詳解
記錄類的核心價(jià)值之一是其自動(dòng)生成的標(biāo)準(zhǔn)方法,這不僅減少了開(kāi)發(fā)者的重復(fù)工作,也保證了行為的一致性和語(yǔ)義清晰性。以下是記錄類在編譯階段自動(dòng)生成的方法列表:
- 所有字段的 訪問(wèn)器方法(accessor)
- 一個(gè) 規(guī)范構(gòu)造器(canonical constructor)
- equals(Object obj) 方法
- hashCode() 方法
- toString() 方法
1. 訪問(wèn)器方法
每個(gè)組件都會(huì)生成一個(gè)訪問(wèn)器方法,其方法名與字段名一致。例如:
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 方法
記錄類會(huì)基于字段值自動(dòng)生成合理的 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比較是基于組件值進(jìn)行的,而非引用。
3. toString 方法
記錄類默認(rèn)實(shí)現(xiàn)了符合邏輯的 toString() 方法,格式為:類名[字段1=值1, 字段2=值2]
System.out.println(user1.toString()); // 輸出:User[username=alice, role=admin]
4. 規(guī)范構(gòu)造器
編譯器會(huì)生成一個(gè)公共構(gòu)造器,其參數(shù)列表與組件順序一致:
public User(String username, String role) {
this.username = username;
this.role = role;
}此構(gòu)造器不能省略字段賦值,且不能繞過(guò)不可變性。
不可變性的特性與價(jià)值
記錄類的一個(gè)顯著特性是:不可變性(Immutability)。一旦創(chuàng)建了記錄類實(shí)例,其狀態(tài)便不可更改,這使得記錄類天然適合用于線程安全的數(shù)據(jù)傳遞、緩存鍵、日志記錄和函數(shù)式編程等場(chǎng)景。
不可變性的實(shí)現(xiàn)方式
在記錄類中:
- 所有組件字段默認(rèn)是
private final,且不能被修改。 - 沒(méi)有生成
setter方法。 - 構(gòu)造函數(shù)中必須為所有字段賦值,且不能通過(guò)反射等方式繞過(guò)字段 final 的限制(除非使用非法手段)。
public record Customer(String id, String name) {
// 無(wú)法修改 id 和 name
}
Customer c1 = new Customer("001", "Alice");
c1.name = "Bob"; // 編譯錯(cuò)誤,字段是 final 的不可變性的優(yōu)勢(shì)
- 線程安全:多個(gè)線程可安全共享記錄類實(shí)例,而無(wú)需加鎖。
- 更少的副作用:狀態(tài)不可更改,避免由于共享狀態(tài)帶來(lái)的錯(cuò)誤。
- 更簡(jiǎn)單的調(diào)試與測(cè)試:數(shù)據(jù)不會(huì)在調(diào)用鏈中被意外修改,提高可預(yù)測(cè)性。
- 易于緩存和哈希結(jié)構(gòu)使用:不可變對(duì)象可作為 Map 的鍵,不會(huì)影響哈希值。
與傳統(tǒng)類的對(duì)比
在傳統(tǒng) Java 類中,需要人為地將字段設(shè)置為 private final,并避免提供 setter 方法,才能勉強(qiáng)模擬不可變性:
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;
}
}記錄類通過(guò)語(yǔ)法層級(jí)的約束,天然支持不可變性,避免了人為失誤。
注意事項(xiàng):深層不可變性
雖然記錄類本身是不可變的,但如果其字段是可變對(duì)象(如 List、Map、數(shù)組),則需要謹(jǐn)慎處理。
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è)可變列表,因此對(duì)象并不是真正不可變。
解決方案:
- 在構(gòu)造函數(shù)中進(jìn)行 defensive copy(防御性復(fù)制)。
- 使用
List.copyOf()創(chuàng)建不可變集合。
public record SafeUserProfile(String username, List<String> tags) {
public SafeUserProfile {
tags = List.copyOf(tags);
}
}這樣就能保證 tags 無(wú)法在外部被修改,真正實(shí)現(xiàn)深層不可變。
構(gòu)造器機(jī)制
盡管記錄類自動(dòng)為我們生成了標(biāo)準(zhǔn)構(gòu)造器,但在實(shí)際開(kāi)發(fā)中,仍有很多場(chǎng)景需要我們自定義構(gòu)造邏輯,比如字段校驗(yàn)、數(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)造器是由編譯器自動(dòng)生成的構(gòu)造函數(shù),其參數(shù)列表與組件順序一致,默認(rèn)行為是將所有字段賦值:
public record Product(String name, double price) {
// 編譯器生成如下構(gòu)造器:
// public Product(String name, double price) {
// this.name = name;
// this.price = price;
// }
}我們也可以顯式地定義規(guī)范構(gòu)造器,來(lái)加入自定義邏輯,例如參數(shù)校驗(yàn):
public record Product(String name, double price) {
public Product {
if (price < 0) {
throw new IllegalArgumentException("價(jià)格不能為負(fù)數(shù)");
}
}
}緊湊構(gòu)造器(Compact Constructor)
Java 為記錄類提供了一種語(yǔ)法糖,稱為“緊湊構(gòu)造器”。在緊湊構(gòu)造器中,我們無(wú)需列出參數(shù)列表,編譯器會(huì)自動(dòng)將構(gòu)造器參數(shù)與字段綁定。
public record Student(String name, int age) {
public Student {
if (age < 0) {
throw new IllegalArgumentException("年齡不能為負(fù)數(shù)");
}
}
}等價(jià)于:
public record Student(String name, int age) {
public Student(String name, int age) {
if (age < 0) {
throw new IllegalArgumentException("年齡不能為負(fù)數(shù)");
}
this.name = name;
this.age = age;
}
}緊湊構(gòu)造器簡(jiǎn)化了字段賦值過(guò)程,適用于多數(shù)校驗(yàn)邏輯場(chǎng)景。
自定義構(gòu)造器與重載
記錄類可以定義額外的構(gòu)造器,但這些構(gòu)造器必須調(diào)用規(guī)范構(gòu)造器,不能繞過(guò)字段賦值流程:
public record Coordinate(int x, int y) {
public Coordinate() {
this(0, 0); // 默認(rèn)構(gòu)造
}
public Coordinate(int value) {
this(value, value); // 重載構(gòu)造
}
}注意:
- 所有非規(guī)范構(gòu)造器都必須顯式調(diào)用
this(...)。 - 不能定義不初始化所有字段的構(gòu)造器。
通過(guò)構(gòu)造器機(jī)制,記錄類不僅保持了不可變性,還為開(kāi)發(fā)者提供了足夠的靈活性,用于字段校驗(yàn)、默認(rèn)值填充、工廠構(gòu)造等實(shí)際需求。
高級(jí)特性
記錄類雖然語(yǔ)法簡(jiǎn)潔,但功能并不簡(jiǎn)單。它支持許多高級(jí)語(yǔ)言特性,包括靜態(tài)成員、接口實(shí)現(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接口實(shí)現(xiàn)
記錄類可以實(shí)現(xiàn)接口(包括函數(shù)式接口),并提供對(duì)應(yīng)方法實(shí)現(xiàn)。
public interface Identifiable {
String id();
}
public record Employee(String id, String name) implements Identifiable {
// 自動(dòng)實(shí)現(xiàn) id() 方法
}記錄類實(shí)現(xiàn)接口時(shí),可以通過(guò)組件直接滿足接口方法簽名,進(jìn)一步提升了類型的表達(dá)力。
泛型支持
記錄類可以定義為泛型類型,從而適用于多種數(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泛型記錄類尤其適用于通用值對(duì)象、鍵值對(duì)封裝、元組等使用場(chǎng)景。
本地記錄類(Local Records)
從 Java 16 開(kāi)始,記錄類也可以在方法內(nèi)部定義,稱為“本地記錄類”,適用于封裝局部方法邏輯中的中間數(shù)據(jù)結(jié)構(gòu)。
public class ReportGenerator {
public void generate() {
record Summary(String title, int count) {}
Summary summary = new Summary("周報(bào)", 12);
System.out.println(summary);
}
}本地記錄類只在方法范圍內(nèi)可見(jiàn),能夠提升臨時(shí)數(shù)據(jù)處理的類型安全性與可讀性,避免引入冗余的外部類定義。
通過(guò)這些高級(jí)特性,記錄類在保持簡(jiǎn)潔的同時(shí),依然具備高度靈活性,能夠應(yīng)對(duì)多種開(kāi)發(fā)需求,適用于從簡(jiǎn)單 DTO 到泛型模型、工具類的廣泛場(chǎng)景。
限制與注意事項(xiàng)
盡管記錄類極大簡(jiǎn)化了不可變數(shù)據(jù)類的開(kāi)發(fā),但其設(shè)計(jì)也存在一些固有的限制,了解這些限制有助于合理使用,避免不合適的場(chǎng)景導(dǎo)致代碼設(shè)計(jì)問(wèn)題。
1. 繼承限制
- 記錄類隱式繼承自
java.lang.Record,不允許繼承其他類。 - 記錄類本身是
final的,不能被繼承。 - 這意味著無(wú)法通過(guò)繼承擴(kuò)展記錄類行為,只能通過(guò)組合或接口實(shí)現(xiàn)進(jìn)行擴(kuò)展。
// 編譯錯(cuò)誤,記錄類不能繼承其他類 public record MyRecord extends SomeClass { } // 編譯錯(cuò)誤,記錄類不能被繼承 public class SubRecord extends MyRecord { }2. 字段限制
- 所有組件字段自動(dòng)為
private final,不能定義非final字段。 - 不支持額外的實(shí)例字段,只能通過(guò)靜態(tài)字段擴(kuò)展類功能。
3. 不支持無(wú)參構(gòu)造器
- 記錄類必須初始化所有組件字段,不能定義無(wú)參構(gòu)造器。
- 但可通過(guò)自定義構(gòu)造器為字段賦默認(rèn)值,實(shí)現(xiàn)類似無(wú)參構(gòu)造器的效果。
4. 不支持可變字段
- 記錄類設(shè)計(jì)為不可變,因此組件字段不能是可變的。
- 如果字段引用了可變對(duì)象,需自行保證深不可變性(如使用不可變集合或防御性復(fù)制)。
5. 限制擴(kuò)展性
- 由于不能繼承和添加實(shí)例字段,記錄類不適合表示需要復(fù)雜行為和狀態(tài)管理的實(shí)體。
- 更適合用于簡(jiǎn)單數(shù)據(jù)載體(如 DTO、值對(duì)象)。
6. 序列化限制
- 記錄類實(shí)現(xiàn)了
Serializable,但序列化時(shí)遵循組件字段的序列化規(guī)則。 - 如果字段不可序列化,會(huì)導(dǎo)致序列化失敗。
理解并遵守這些限制,有助于避免誤用記錄類導(dǎo)致設(shè)計(jì)不佳和維護(hù)困難。
到此這篇關(guān)于Java 記錄類Record詳解的文章就介紹到這了,更多相關(guān)Java Record 類內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
java 根據(jù)坐標(biāo)截取圖片實(shí)例代碼
這篇文章主要介紹了java 根據(jù)坐標(biāo)截取圖片實(shí)例代碼的相關(guān)資料,需要的朋友可以參考下2017-03-03
JDK從8升級(jí)到21的問(wèn)題集(附案例代碼)
JDK 8升級(jí)到JDK 21是一個(gè)重要的版本遷移,涉及語(yǔ)法、模塊化、API變更等多方面調(diào)整,這篇文章主要介紹了JDK從8升級(jí)到21問(wèn)題的相關(guān)資料,文中通過(guò)代碼介紹的非常詳細(xì),需要的朋友可以參考下2025-09-09
mybatisPlus 實(shí)體類與數(shù)據(jù)庫(kù)表映射關(guān)系詳解
這篇文章主要介紹了mybatisPlus 實(shí)體類與數(shù)據(jù)庫(kù)表映射關(guān)系詳解,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教。2022-01-01
Java利用MessageFormat實(shí)現(xiàn)短信模板的匹配
這篇文章主要介紹了Java利用MessageFormat實(shí)現(xiàn)短信模板的匹配,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-06-06
SpringBoot的ConfigurationProperties或Value注解無(wú)效問(wèn)題及解決
在SpringBoot項(xiàng)目開(kāi)發(fā)中,全局靜態(tài)配置類讀取application.yml或application.properties文件時(shí),可能會(huì)遇到配置值始終為null的問(wèn)題,這通常是因?yàn)樵趧?chuàng)建靜態(tài)屬性后,IDE自動(dòng)生成的Get/Set方法包含了static關(guān)鍵字2024-11-11
Spring Boot和Docker實(shí)現(xiàn)微服務(wù)部署的方法
這篇文章主要介紹了Spring Boot和Docker實(shí)現(xiàn)微服務(wù)部署的方法,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2019-01-01
Java?map和bean互轉(zhuǎn)常用的方法總結(jié)
這篇文章主要給大家介紹了關(guān)于Java中map和bean互轉(zhuǎn)常用方法的相關(guān)資料,平時(shí)日常Java開(kāi)發(fā),經(jīng)常會(huì)涉及到Java?Bean和Map之間的類型轉(zhuǎn)換,需要的朋友可以參考下2023-09-09

