Java 14 釋出了,可以扔掉Lombok了?
阿新 • • 發佈:2020-03-20
2020年3月17日釋出,Java正式釋出了JDK 14 ,目前已經可以開放下載。在JDK 14中,共有16個新特性,本文主要來介紹其中的一個特性:JEP 359: Records
### 官方吐槽最為致命
早在2019年2月份,Java 語言架構師 Brian Goetz,曾經寫過一篇文章(http://cr.openjdk.java.net/~briangoetz/amber/datum.html ),詳盡的說明了並吐槽了Java語言,他和很多程式設計師一樣抱怨“Java太囉嗦”或有太多的“繁文縟節”,他提到:開發人員想要建立**純資料載體**類(plain data carriers)通常都必須編寫大量低價值、重複的、容易出錯的程式碼。如:建構函式、getter/setter、equals()、hashCode()以及toString()等。
以至於很多人選擇使用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等方法。
於是,BrianGoetz大神提出一種想法,他提到,Java完全可以對於這種純資料載體通過另外一種方式表示。
其實在其他的面嚮物件語言中,早就針對這種純資料載體有單獨的定義了,如Scala中的case、Kotlin中的data以及C#中的record。這些定義,儘管在語義上有所不同,但是它們的共同點是類的部分或全部狀態可以直接在類頭中描述,並且這個類中只包含了純資料而已。
於是,他提出Java中是不是也可以通過如下方式定義一個純資料載體呢?
record Point(int x, int y) { }
### 神說要用record,於是就有了
就像大神吐槽的那樣,我們通常需要編寫大量程式碼才能使類變得有用。如以下內容:
* toString()方法
* hashCode() and equals()方法
* Getter 方法
* 一個共有的建構函式
對於這種簡單的類,這些方法通常是無聊的、重複的,而且是可以很容易地機械地生成的那種東西(ide通常提供這種功能)。
當你閱讀別人的程式碼時,可能會更加頭大。例如,別人可能使用IDE生成的hashCode()和equals()來處理類的所有欄位,但是如何才能在不檢查實現的每一行的情況下確定他寫的對呢?如果在重構過程中添加了欄位而沒有重新生成方法,會發生什麼情況呢?
大神Brian Goetz提出了使用record定義一個純資料載體的想法,於是,Java 14 中便包含了一個新特性:EP 359: Records ,作者正是 Brian Goetz
![][1]
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();
}
通過反編譯得到的類,我們可以得到以下資訊:
1、生成了一個final型別的Person類(class),說明這個類不能再有子類了。
2、這個類繼承了java.lang.Record類,這個我們使用enum創建出來的列舉都預設繼承java.lang.Enum有點類似
3、類中有兩個private final 型別的屬性。所以,record定義的類中的屬性都應該是private final型別的。
4、有一個public的建構函式,入參就是兩個主要的屬性。如果通過位元組碼檢視其方法體的話,其內容就是以下程式碼,你一定很熟悉:
public Person(String firstName, String lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
5、有兩個getter方法,分別叫做firstName和lastName。這和JavaBean中定義的命名方式有區別,或許大神想通過這種方式告訴我們record定義出來的並不是一個JavaBean吧。
6、還幫我們自動生成了toString(), hashCode() 和 equals()方法。值得一提的是,這三個方法依賴invokedynamic來動態呼叫包含隱式實現的適當方法。
### 還可以這樣玩
前面的例子中,我們簡單的建立了一個record,那麼,record中還能有其他的成員變數和方法嗎?我們來看下。
1、我們不能將例項欄位新增到record中。但是,我們可以新增靜態欄位。
record Person (String firstName, String lastName) {
static int x;
}
2、我們可以定義靜態方法和例項方法,可以操作物件的狀態。
record Person (String firstName, String lastName) {
static int x;
public static void doX(){
x++;
}
public String getFullName(){
return firstName + " " + lastName;
}
}
3、我們還可以新增建構函式。
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都還沒用熟~
參考資料:
https://openjdk.java.net/jeps/359
https://dzone.com/articles/a-first-look-at-records-in-java-14
https://aboullaite.me/java-14-records/
http://cr.openjdk.java.net/~briangoetz/amber/datum.html
[1]: https://www.hollischuang.com/wp-content/uploads/2020/03/15846226448634.jpg