Java 14 發佈了,終於可以扔掉Lombok了?

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

 

 

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都還沒用熟~

 

PS:最近Java 14發佈了很多新功能,很多特性還是比較有意思的,這個是本系列的第一篇文章,歡迎大家關注公衆號:Java之道,後續文章寫好後會第一時間發出。

 

參考資料:

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

 

關於作者Hollis,一個對Coding有着獨特追求的人,現任阿里巴巴技術專家,個人技術博主,技術文章全網閱讀量數千萬,《程序員的三門課》聯合作者。

轉載自 微信公衆號 Java之道

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章