Java14發布!不使用"class"也能定義類了?還順手要幹掉Lombok!

2021-01-16 網易新聞

作者:HollisChuang 來源:掘金 連結:https://juejin.im/post/5e7421b8518825497468178a

2020年3月17日發布,Java正式發布了JDK 14 ,目前已經可以開放下載。在JDK 14中,共有16個新特性,本文主要來介紹其中的一個特性:JEP 359: Records

官方吐槽最為致命

早在2019年2月份,Java 語言架構師 Brian Goetz,曾經寫過一篇文章(cr.openjdk.java.net/~briangoetz… ),詳盡的說明了並吐槽了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.javaNote: 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都還沒用熟~

特別聲明:以上內容(如有圖片或視頻亦包括在內)為自媒體平臺「網易號」用戶上傳並發布,本平臺僅提供信息存儲服務。

相關焦點

  • 一份不可多得的 Lombok 學習指南|log|interface|data|object_網易...
    這裡不詳細展開,具體的安裝方式可以參考:https://www.baeldung.com/lombok-ide。  三、Lombok 詳解  注意:以下示例所使用的 Lombok 版本是 1.18.10  3.1 @Getter and @Setter  你可以使用 或 注釋任何類或欄位,Lombok 會自動生成默認的 方法。
  • 通用Mapper 4.1.0 發布,大量新功能 PR 合併
    priority = 2) 註解,可以配置排序的優先級 by qrqhuangcy #457增加 Oracle 批量插入方法,在 OracleMapper 中 by qrqhuangcy #467通過反射設置主鍵值,解決 issue #445 和 #461,mysql 和 sqlserver 特殊的插入操作不在需要指定
  • 使用Python super方法為您的類增強
    雖然可能性受到您的想像力的限制,但常見的用例是構建擴展先前構建的類的功能的類。調用以前構建的方法super()可以使您無需在子類中重寫這些方法,並允許您使用最少的代碼更改來交換超類。super() 在單一繼承中如果您不熟悉面向對象的編程概念,繼承可能是一個不熟悉的術語。
  • 推挽式AB類定義及交越失真之解惑
    謝超:唐老師,我這句話沒理解,怎麼從class B轉換到class AB的?唐長文:首先,你理解Class A, Class B和Class AB,三種類型的定義嗎?它們的波形怎樣,你清楚嗎?謝超:他們是根據導通角定義的,class A導通角是360°,class AB 180~360°,class B 180°,學的時候都是在功放裡邊講的,作為輸出級我理解的就不太好了。
  • 你知道java反射機制中class.forName和classloader的區別嗎?
    前兩天頭條有朋友留言說使用class.forName找不到類,可以使用classloader加載。趁此機會總結一下,正好看到面試中還經常問到。一、類加載機制上面兩種加載類的方式說到底還是為了加載一個java類,因此需要先對類加載的過程進行一個簡單的了解。
  • Java內部類新解,你沒有見過的船新版本
    總的來說,嵌套類最大的目的是改善代碼的組織,並不是必不可少的功能。嵌套類能實現的功能,通過正常的類都可以實現,只不過可能要多寫點代碼,且不是很優雅而已。外層類定義了1個公有變量、2個私有變量和1個私有方法。然後在InnerClass裡面直接使用了OuterClass的所有成員。
  • 高樓萬丈平地起,基礎要打牢!Python獲取類的層次結構和繼承順序
    再簡單點理解,就是用它可以知道類的層次結構和繼承順序。對著一個簡單的例子來理解下,千萬別走開哦……定義示例類舉個例子首先定義四個相互繼承的類A、B、C、Dclass A(object):passclass B(A):passclass C(B):
  • 淺析java繼承與多態,抽象類與接口的區別
    但是 Java 不支持多繼承,即 M 同時繼承 A 又 繼承 B,Java 不允許多繼承降低了複雜度,如果要實現多繼承,可以通過接口 interface 形式實現。所以 Java 類之間只有單一繼承關係,沒有多繼承。問什麼這樣設計,要問語言設計者。
  • 分類問題-----多標籤(multilabel)、多類別(multiclass)
    3、多標籤分類問題的定義        簡單的說就是同一個實例,可以有多個標籤, 或者被分為多個類。和多分類的區別是, 多分類中每個實例只有一個標籤。下面是幾個形式化的定義。    用這種方法會導致訓練樣本的損失,所以不推薦使用。還可以將訓練樣本按每個標籤構造一個訓練集,每個樣本屬於或不屬於這個標籤,對每個標籤單獨訓練一個分類器,然後將多個分類器的結果合成。還有將每個多標籤單獨看做一個新標籤,在一個更多的標籤集上做多分類。當多標籤樣本比較少時,這個方法就比較受限。
  • Meta CSS框架發布
    在一些可以控制的情況下,定義太多特殊類名用來區分,又會造成大量後期維護的困難。  3.良好的運用css組合方式,可以比較妥善的解決上面的2個問題。首先,公用樣式都被提取了,你修改一個按鈕,只需要改公共的部分。其次,不需要絞 盡腦汁去想一個不會衝突的css類名,遵循組合的規則就可以了。
  • 黑馬程式設計師:Python封裝、繼承和多態以及類方法等習題
    使用public關鍵字 C.使用__XX__定義屬性名 D.使用__XX定義屬性名2.下列選項中,不屬於面向對象程序設計的三個特徵的是()。類屬性既可以顯示定義,又能在方法中定義B. 公有類屬性可以通過類和類的實例訪問C. 通過類可以獲取實例屬性的值D. 類的實例只能獲取實例屬性的值6. 下列選項中,用於標識為靜態方法的是()。
  • java第五章 類和對象(核心)
    調用show方法s.show();}}4.2 private的使用【應用】需求:定義標準的學生類,要求name和age使用private修飾,並提供set和get方法以及便於顯示數據的show方法,測試類中創建對象並使用,最終控制臺輸出 林青霞,30示例代碼:/*學生類*/
  • 新京報:幹掉一個壞翻譯,拯救不了銀河系
    有網友還提出了兩個方案:一、請網絡字幕組翻譯將來的這類大片;二、加上中英文雙字幕,以使有一定英文能力的人可以不受中文爛字幕的幹擾。我覺得大家似乎都想把《英雄》的那句宣傳語改成「幹掉壞翻譯,拯救銀河系」了。   賈秀琰的翻譯好不好?
  • 簡析:Java反射、Java反射定義、反射的基石
    Java反射定義在程序運行過程中,對於任意一個類,可以獲得該類的屬性和方法;對於任意一個對象,可以調用該對象的任意一個屬性和方法。在運行時動態獲取類的信息和動態調用對象的屬性和方法稱為Java反射機制。
  • Java之抽象方法的使用與及接口中抽象方法的簡單介紹
    >:常量,抽象方法,默認方法,靜態方法,私有方法接下來,小編要講的是,接口中抽象方法的定義和使用,首先是抽象方法的定義,格式如下:public abstract 返回值類型 方法名稱(參數列表){方法體}(接口當中的默認方法
  • 幹掉一個壞翻譯,拯救不了銀河系
    有網友還提出了兩個方案:一、請網絡字幕組翻譯將來的這類大片;二、加上中英文雙字幕,以使有一定英文能力的人可以不受中文爛字幕的幹擾。我覺得大家似乎都想把《英雄》的那句宣傳語改成「幹掉壞翻譯,拯救銀河系」了。   賈秀琰的翻譯好不好?
  • Java之成員內部類詳解
    前言在上文中,講到了靜態內部類,本文主要談一下成員內部類、局部內部類和匿名內部類。成員內部類和靜態內部類非常相似,都是定義在一個類中的成員位置,與靜態內部類唯一的區別是,成員內部類沒有static修飾。