2020年3月17日發佈,Java正式發佈了JDK 14 ,目前已經能夠開放下載。在JDK 14中,共有16個新特性,本文主要來介紹其中的一個特性:JEP 359: Recordshtml
早在2019年2月份,Java 語言架構師 Brian Goetz,曾經寫過一篇文章(cr.openjdk.java.net/~briangoetz… ),詳盡的說明了並吐槽了Java語言,他和不少程序員同樣抱怨「Java太囉嗦」或有太多的「繁文縟節」,他提到:開發人員想要建立純數據載體類(plain data carriers)一般都必須編寫大量低價值、重複的、容易出錯的代碼。如:構造函數、getter/setter、equals()、hashCode()以及toString()等。java
以致於不少人選擇使用IDE的功能來自動生成這些代碼。還有一些開發會選擇使用一些第三方類庫,如Lombok等來生成這些方法,從而會致使了使人吃驚的表現(surprising behavior)和糟糕的可調試性(poor debuggability)。程序員
那麼,Brian Goetz 大神提到的純數據載體到底指的是什麼呢。他舉了一個簡單的例子:架構
final class Point {
public final int x;
public final int y;
public Point(int x, int y) {
this.x = x;
this.y = y;
}
// state-based implementations of equals, hashCode, toString
// nothing else
}
複製代碼
這裏面的Piont其實就是一個純數據載體,他表示一個"點"中包含x座標和y座標,而且只提供了構造函數,以及一些equals、hashCode等方法。ide
因而,BrianGoetz大神提出一種想法,他提到,Java徹底能夠對於這種純數據載體經過另一種方式表示。函數
其實在其餘的面嚮對象語言中,早就針對這種純數據載體有單獨的定義了,如Scala中的case、Kotlin中的data以及C#中的record。這些定義,儘管在語義上有所不一樣,可是它們的共同點是類的部分或所有狀態能夠直接在類頭中描述,而且這個類中只包含了純數據而已。this
因而,他提出Java中是否是也能夠經過以下方式定義一個純數據載體呢?spa
record Point(int x, int y) { }
複製代碼
就像大神吐槽的那樣,咱們一般須要編寫大量代碼才能使類變得有用。如如下內容:.net
對於這種簡單的類,這些方法一般是無聊的、重複的,並且是能夠很容易地機械地生成的那種東西(ide一般提供這種功能)。debug
當你閱讀別人的代碼時,可能會更加頭大。例如,別人可能使用IDE生成的hashCode()和equals()來處理類的全部字段,可是如何才能在不檢查實現的每一行的狀況下肯定他寫的對呢?若是在重構過程當中添加了字段而沒有從新生成方法,會發生什麼狀況呢?
大神Brian Goetz提出了使用record定義一個純數據載體的想法,因而,Java 14 中便包含了一個新特性:EP 359: Records ,做者正是 Brian Goetz
Records的目標是擴展Java語言語法,Records爲聲明類提供了一種緊湊的語法,用於建立一種類中是「字段,只是字段,除了字段什麼都沒有」的類。經過對類作這樣的聲明,編譯器能夠經過自動建立全部方法並讓全部字段參與hashCode()等方法。這是JDK 14中的一個預覽特性。
Records的用法比較簡單,和定義Java類同樣:
record Person (String firstName, String lastName) {}
複製代碼
如上,咱們定義了一個Person記錄,其中包含兩個組件:firstName和lastName,以及一個空的類體。
那麼,這個東西看上去也是個語法糖,那他究竟是怎麼實現的那?
咱們先嚐試對他進行編譯,記得使用--enable-preview
參數,由於records功能目前在JDK 14中仍是一個預覽(preview)功能。
> javac --enable-preview --release 14 Person.java
Note: Person.java uses preview language features.
Note: Recompile with -Xlint:preview for details.
複製代碼
如前所述,Record只是一個類,其目的是保存和公開數據。讓咱們看看用javap進行反編譯,將會獲得如下代碼:
public final class Person extends java.lang.Record {
private final String firstName;
private final String lastName;
public Person(java.lang.String, java.lang.String);
public java.lang.String toString();
public final int hashCode();
public final boolean equals(java.lang.Object);
public java.lang.String firstName();
public java.lang.String lastName();
}
複製代碼
經過反編譯獲得的類,咱們能夠獲得如下信息:
一、生成了一個final類型的Person類(class),說明這個類不能再有子類了。
二、這個類繼承了java.lang.Record類,這個咱們使用enum建立出來的枚舉都默認繼承java.lang.Enum有點相似
三、類中有兩個private final 類型的屬性。因此,record定義的類中的屬性都應該是private final類型的。
四、有一個public的構造函數,入參就是兩個主要的屬性。若是經過字節碼查看其方法體的話,其內容就是如下代碼,你必定很熟悉:
public Person(String firstName, String lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
複製代碼
五、有兩個getter方法,分別叫作firstName和lastName。這和JavaBean中定義的命名方式有區別,或許大神想經過這種方式告訴咱們record定義出來的並非一個JavaBean吧。
六、還幫咱們自動生成了toString(), hashCode() 和 equals()方法。值得一提的是,這三個方法依賴invokedynamic來動態調用包含隱式實現的適當方法。
前面的例子中,咱們簡單的建立了一個record,那麼,record中還能有其餘的成員變量和方法嗎?咱們來看下。
一、咱們不能將實例字段添加到record中。可是,咱們能夠添加靜態字段。
record Person (String firstName, String lastName) {
static int x;
}
複製代碼
二、咱們能夠定義靜態方法和實例方法,能夠操做對象的狀態。
record Person (String firstName, String lastName) {
static int x;
public static void doX(){
x++;
}
public String getFullName(){
return firstName + " " + lastName;
}
}
複製代碼
三、咱們還能夠添加構造函數。
record Person (String firstName, String lastName) {
static int x;
public Person{
if(firstName == null){
throw new IllegalArgumentException( "firstName can not be null !");
}
}
public Person(String fullName){
this(fullName.split(" ")[0],this(fullName.split(" ")[1])
}
}
複製代碼
因此,咱們是能夠在record中添加靜態字段/方法的,可是問題是,咱們應該這麼作嗎?
請記住,record推出背後的目標是使開發人員可以將相關字段做爲單個不可變數據項組合在一塊兒,而不須要編寫冗長的代碼。這意味着,每當您想要向您的記錄添加更多的字段/方法時,請考慮是否應該使用完整的類來代替它。
record 解決了使用類做爲數據包裝器的一個常見問題。純數據類從幾行代碼顯著地簡化爲一行代碼。
可是,record目前是一種預覽語言特性,這意味着,儘管它已經徹底實現,但在JDK中尚未標準化。
那麼問題來了,若是你用上了Java 14以後,你還會使用Lombok嗎?哦不,你可能短期內都用不上,由於你可能Java 8都還沒用熟~
參考資料:
cr.openjdk.java.net/~briangoetz…
關於做者:Hollis,一個對Coding有着獨特追求的人,某互聯網公司技術專家,我的技術博主,技術文章全網閱讀量數千萬,《程序員的三門課》聯合做者。