您好,登錄后才能下訂單哦!
小編給大家分享一下Java中記錄類型的示例分析,相信大部分人都還不怎么了解,因此分享這篇文章給大家參考一下,希望大家閱讀完這篇文章后大有收獲,下面讓我們一起去了解一下吧!
在本文中,我們將看到 Oracle with Java 16 如何正式引入除類、接口、枚舉和注釋之外的第五種 Java 類型:記錄類型。記錄是使用非常綜合的語法定義的特定類。它們旨在實現表示數據的類。
特別是,記錄旨在表示不可變的數據容器。記錄語法可幫助開發人員專注于設計數據,而不會迷失在實現細節中。
記錄的語法是最小的:
[modifiers] record identifier (header) {[members]}
術語header
是指由逗號分隔的變量聲明列表,它將代表記錄的實例變量。一條記錄隱式定義了一個構造函數,該構造函數將標頭作為參數列表,定義標頭中聲明的所有字段的訪問器方法,并提供toString
,equals
和hashCode
方法的默認實現。
讓我們馬上看一個例子。因此,假設我們要編寫一個拍賣畫作銷售應用程序。這些將被理解為不可變對象。事實上,一旦它們被出售,它們就無法改變。例如,一幅畫在被定義后就不能改變它的標題。然后我們可以創建Painting
記錄:
public record Painting(String title, String author, int price) { }
我們可以實例化這條記錄,就好像它是一個類,它有一個用頭參數列表定義的構造函數:
Painting painting = new Painting("Camaleón", "Leonardo Furino", 1000000);
由于記錄也自動定義了toString 方法,以下代碼片段:
System.out.println(painting);
將產生輸出:
Painting[title=Camaleón, author=Leonardo Furino, price=1000000]
因此,記錄的明顯優勢之一是極其綜合的語法。
記錄類型和枚舉類型之間有明顯的相似之處。這兩種類型都在特定情況下替換了類。枚舉旨在表示相同類型的定義數量的常量實例。另一方面,記錄應該代表不可變的數據容器。與枚舉一樣,記錄也通過提供比類更少冗長的語法和簡單、清晰的規則來簡化開發人員的工作。
這些記錄僅在 Java 14 中作為功能預覽引入,并在 Java 16 中正式發布。與往常一樣,Java 通過將將記錄轉換為類的任務委托給編譯器以保持與舊程序的向后兼容性來減輕這一新功能的影響。具體來說,當枚舉被編譯器轉換成擴展抽象java.lang.Enum類的類時,記錄被編譯器轉換成擴展抽象java.lang.Record類的類。
對于Enum類,編譯器將不允許開發人員創建直接擴展Record類的類。事實上,它也是一個特殊的類,專門為支持記錄的概念而創建。
當我們編譯Painting.java文件時,我們會得到Painting.class文件。在這個文件中,編譯器將插入一個Painting類(記錄轉換的結果):
被聲明final;
定義一個將標頭作為參數列表的構造函數。
定義標頭中聲明的所有字段的訪問器方法。
覆蓋Object方法:toString,equals和hashCode。
實際上,JDK javap 工具允許我們Painting.class使用以下命令通過自省讀取生成的類的結構:
javap Painting.class
Compiled from " Painting.java"
public final class Painting extends java.lang.Record {
public Painting(java.lang.String, java.lang.String, int);
public java.lang.String toString();
public final int hashCode();
public final boolean equals(java.lang.Object);
public java.lang.String title();
public java.lang.String author();
public int price();
}
請注意,訪問器方法標識符不遵循我們迄今為止使用的通常約定。而不是被調用getTitle, getAuthor并且getPrice它們被簡單地稱為title,author和price,但功能保持不變。
因此,我們可以使用以下語法對記錄的各個字段進行讀取訪問:
String title = painting.title();
String author = painting.author();
如果我們創建了一個Painting與記錄等效的類,我們將不得不手動編寫以下代碼:
public final class Painting {
private String title;
private String author;
private int price;
public Painting(String title, String author, int price) {
this.title = title;
this.author = author;
this.price = price;
}
public String title() {
return title;
}
public String author() {
return author;
}
public int price() {
return price;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((author == null) ? 0 : author.hashCode());
result = prime * result + price;
result = prime * result + ((title == null) ? 0 : title.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Painting other = (Painting) obj;
if (author == null) {
if (other.author != null)
return false;
} else if (!author.equals(other.author))
return false;
if (price != other.price)
return false;
if (title == null) {
if (other.title != null)
return false;
} else if (!title.equals(other.title))
return false;
return true;
}
@Override
public String toString() {
return "Painting [title=" + title + ", author=" + author + ", price="
+ price + "]" ;
}
}
顯然,在這種情況下,定義記錄而不是類無疑更方便,盡管 IDE 仍然允許我們對此類進行半自動開發。
記錄旨在表示攜帶不可變數據的對象。因此,記錄繼承是不可實現的。特別是,記錄不能擴展,因為記錄是自動聲明的final。此外,記錄不能擴展類(顯然不能擴展記錄),因為它已經擴展了Record類。
這是一個看似有限的選擇,但它符合使用記錄的理念。記錄必須是不可變的,并且繼承與不變性不兼容。但是,通過隱式擴展Record類,記錄繼承了該類的方法。實際上,Record該類僅覆蓋了從Object該類繼承的 3 個方法:toString、equals和hashCode,并沒有定義新方法。
在記錄中,我們還可以覆蓋訪問器方法和Object編譯器在編譯時生成的三個方法。事實上,如果需要,在我們的代碼中顯式聲明它們以自定義和優化它們可能很有用。例如,我們可以自定義記錄中的toString方法Painting如下:
public record Painting(String title, String author, int price) {
@Override
public String toString() {
return "The painting " + title + " by " + author + " costs " + price;
}
}
我們也已經知道記錄和枚舉一樣,不能擴展,也不能擴展其他類或記錄。但是,記錄可以實現接口。
與枚舉一樣,記錄也是隱式的final,因此abstract不能使用修飾符。所以,當我們在一個記錄中實現一個接口時,我們必須實現所有繼承的方法。
不可能在記錄中聲明實例變量和實例初始值設定項。這是為了不違反記錄的作用,記錄應該代表不可變數據的容器。
相反,你可以聲明靜態方法、變量和初始值設定項。事實上,這些是靜態的,由記錄的所有實例共享,并且不能訪問特定對象的實例成員。
但是自定義記錄最有趣的部分是能夠創建構造函數。
我們知道,在一個類中如果不添加構造函數,編譯器會添加一個無參數的構造函數,稱為默認構造函數。當我們在類中顯式添加構造函數時,無論其參數數量是多少,編譯器都將不再添加默認構造函數。
然而,在記錄中,自動添加編譯器的構造函數將記錄頭中定義的變量定義為參數。此構造函數稱為規范構造函數。在它的特性中,它是唯一允許設置記錄的實例變量的構造函數(我們很快就會看到)。也就是說,我們定義構造函數的選項如下:
顯式地重新定義規范構造函數,最好使用其緊湊形式。
定義一個調用規范構造函數的非規范構造函數。
我們可以顯式聲明一個規范的構造函數。例如,如果我們想在設置實例變量的值之前添加一致性檢查,這會很有用。例如,考慮以下抽象照片概念的記錄,我們向其顯式添加規范構造函數:
public record Photo(String format, boolean color) {
public Photo(String format, boolean color) {
if (format.length() < 5) throw new
IllegalArgumentException("Format description too short");
this.format = format;
this.color = color;
}
}
注意初始化實例變量是必須的,否則編譯器會報錯。例如,如果我們不初始化格式變量,我們將收到以下錯誤:
error: variable format might not have been initialized
}
^
1 error
在這種情況下,我們顯式地創建了一個規范構造函數,它必須定義在記錄頭中定義的相同參數列表。但是,我們可以通過使用其緊湊形式來更輕松地創建顯式規范構造函數。
確實可以創建一個緊湊的規范構造函數。它的特點是不聲明參數列表。這并不意味著它將有一個空的參數列表,而是圓括號不會出現在構造函數的標識符旁邊。因此,讓我們重寫一個與前面示例等效的構造函數:
public Photo {
if (format.length() < 5) throw new IllegalArgumentException(
"Format description too short");
}
緊湊規范構造函數的使用應被視為在記錄中顯式定義構造函數的標準方法。請注意,甚至不需要初始化自動初始化的實例變量。更準確地說,如果我們嘗試在緊湊的規范構造函數中初始化實例變量,我們將得到一個編譯時錯誤。
也可以定義一個參數列表不同于規范構造函數的構造函數,即非規范構造函數。在這種情況下,我們正在執行構造函數重載。事實上,與類中默認構造函數的情況不同,添加具有不同參數列表的構造函數無論如何都不會阻止編譯器添加規范構造函數。此外,非規范構造函數必須調用另一個構造函數作為其第一條語句。事實上,如果我們添加如下構造函數:
public Photo(String format, boolean color, boolean msg) {
if (format.length() < 5) throw new IllegalArgumentException(msg);
this.format = format;
this.color = color;
}
我們會得到一個編譯時錯誤:
Error: constructor is not canonical, so its first statement must invoke another constructor
public Photo(String format, boolean color, String msg) {
^
1 error
顯然,如果我們添加另一個非規范構造函數來調用,遲早會調用(顯式或隱式)規范構造函數。在我們的例子中,如果我們然后直接調用規范構造函數,我們還必須刪除設置實例變量的指令,因為這些將在非規范構造函數的第一行中被調用后由規范構造函數設置構造函數。事實上,下面的構造函數:
public Photo(String format, boolean color, String msg) {
this(format, color);
if (format.length() < 5) throw new IllegalArgumentException(msg);
this.format = format;
this.color = color;
}
會導致以下編譯錯誤:
error: variable format might already have been assigned
this.format = format;
^
error: variable color might already have been assigned
this.color = color;
^
2 errors
這表明這兩個變量此時已經被初始化。這表明規范構造函數始終負責設置記錄的實例變量。所以我們只需要刪除不必要的行:
public Photo(String format, boolean color, String msg) { this(format, color); if (format.length() < 5) throw new IllegalArgumentException(msg);}
在這一點上,我們將能夠Photo使用規范構造函數和非規范構造函數從記錄創建對象。例如:
var photo1 = new Photo("Photo 1" , true); // canonical constructor
System.out.println(photo1);
var photo2 = new Photo("Photo 2" , false, "Error!"); // non-canonical constructor
System.out.println(photo2);
var photo3 = new Photo("Photo" , true, "Error!"); // non-canonical constructor
System.out.println(photo3);
前面的代碼將打印輸出:
Photo[format=Photo 1, color=true]
Photo[format=Photo 2, color=false]
Exception in thread "main" java.lang.IllegalArgumentException: Error!
at Photo.<init>(Photo.java:8)
at TestRecordConstructors.main(TestRecordConstructors.java:7)
何時使用記錄而不是類應該已經很清楚了。如上所述,記錄旨在表示不可變的數據容器。記錄不能總是用來代替類,尤其是當這些類主要定義業務方法時。
然而,軟件的本質是進化。因此,即使我們創建一個記錄來表示一個不可變數據的容器,也不一定有一天將其轉換為一個類是不合適的。應該引導我們更喜歡以類的形式重寫記錄的一個線索是,當我們添加了太多方法或擴展了太多接口時。在這種情況下,值得詢問記錄是否需要轉換為類。
由于其不可變的性質,記錄非常適合密封接口。此外,它通常不代表聚合大量實例變量的概念。
記錄的概念似乎非常適合稱為 DTO(數據傳輸對象的首字母縮寫詞)的設計模式的實現。
以上是“Java中記錄類型的示例分析”這篇文章的所有內容,感謝各位的閱讀!相信大家都有了一定的了解,希望分享的內容對大家有所幫助,如果還想學習更多知識,歡迎關注億速云行業資訊頻道!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。