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

2020-12-13 網易新聞

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

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

相關焦點

  • Java 14發布了,不使用"class"也能定義類了?還順手要幹掉Lombok!
    例如,別人可能使用IDE生成的hashCode()和equals()來處理類的所有欄位,但是如何才能在不檢查實現的每一行的情況下確定他寫的對呢?如果在重構過程中添加了欄位而沒有重新生成方法,會發生什麼情況呢?
  • 迷茫了,我們到底該不該用lombok?
    User類的主體只用定義成員變量,其他的方法全都交給註解來完成。如果修改了成員變量名稱或者類型,怎麼辦呢?但是這種方式效率其實不高,要在程序運行起來才能解析。這時候編譯時解析就體現出它的價值了。(AST),增加getter和setter方法定義的相應樹節點javac使用修改後的抽象語法樹(AST)生成字節碼文件,即給class增加新的節點(代碼塊)為什麼建議不用lombok?
  • Lombok的@Builder不好使?來試試這個
    Builder註解的使用限制當我們的實體對象有繼承的設計的時候,Builder註解就沒那麼好用了,還是以商品實體為例,如果現在商品類都繼承自一個BaseDTO@Builder@NoArgsConstructorpublic class BaseDTO { /** * 業務身份 */ private String bizType;
  • 記一次使用 Lombok 造成的事故!
    插入數據的時候,發現,其他屬性都能正常的插入,但是就是nMetaType屬性在資料庫一直是null.解決當我debug項目代碼到調用Mybatis的插入SQL對應的方法的時候,我看到NMetaVerify對象的nMetaType屬性還是有數據的,但是執行插入之後,資料庫的nMetaType欄位就是一直是null,原先我以為是我的枚舉類型寫法不正確,看了下別的同樣具有枚舉類型的欄位,也是正常能插入到資料庫當中的,這更讓我感覺到疑惑了
  • 一文讀懂 TypeScript Class (類)
    在ES6中有了類的概念,雖然它的本質依舊是基於原型的,但是能夠讓開發者更舒服的使用class了。TypeScript 作為 JavaScript 的超集,自然也是支持 class 全部特性的,並且還可以對類的屬性、方法等進行靜態類型檢測。下面就來看看 TypeScript 中的class。一、類的概念 1.
  • Java 14 發布了,可以扔掉Lombok了?
    2020年3月17日發布,Java正式發布了JDK 14 ,目前已經可以開放下載。例如,別人可能使用IDE生成的hashCode()和equals()來處理類的所有欄位,但是如何才能在不檢查實現的每一行的情況下確定他寫的對呢?如果在重構過程中添加了欄位而沒有重新生成方法,會發生什麼情況呢?
  • Java基礎入門篇之面向對象和類的定義
    Java基礎入門篇之面向對象和類的定義 本文主要介紹了面向對象概念,面向對象的三個特點封裝性、繼承性、多態性。類的定義和創建對象與使用。詳細的介紹了怎麼去定義一個類,通過案例來理解怎麼去定義的。對象的創建與使用介紹了使用的格式,通過創建對象後,可以通過對象的引用來訪問對象的成員。
  • Python進階版:定義類時應用的9種最佳做法
    在這些情況下,定義自己的類就很划得來了,這樣一來你可以對相關信息進行分組並且改善項目的結構設計。而且由於你即將處理更少的分段代碼,代碼庫的長期可維護性將得到改善。但要注意,僅當以正確方式完成類聲明時,操作才可以實現,定義自定義類的益處才能超過管理它們的支出。
  • 使用Python super方法為您的類增強
    你為什麼要這樣做呢?雖然可能性受到您的想像力的限制,但常見的用例是構建擴展先前構建的類的功能的類。調用以前構建的方法super()可以使您無需在子類中重寫這些方法,並允許您使用最少的代碼更改來交換超類。
  • Java之使用Lambda表達式,定義一個無參數無返回值的方法
    Java之Runnable表達式的簡單介紹,這次小編要介紹的是,使用Lambda表達式,定義一個無參數無返回值的方法使用Lambda的標準格式調用involvePhone方法,列印輸出「打電話」字樣。方法,參數是Phone接口,使用Phone接口的匿名內部類對象invokePhone(new Phone() {public void ringUp() {System.out.println("打電話");}
  • 33 class選擇器和原子類
    class選擇器使用.前綴+類名來選擇指定class的標籤。比如.warning就是class選擇器,它表示選擇class屬性值為"warning"的標籤。class類名有以下特點:1.多個標籤可以使用相同的類名我們知道一個頁面上,id名是唯一的,一個id名只能被一個標籤所使用,但是class名就靈活多了,一個class名可以被多個標籤所使用。
  • 什麼是類,什麼是對象
    一什麼是類?類是對象的「靈魂」。對象可以是任何事物,而類不會做任何事情,也不會佔用內存,只有當類成為對象並使用Set語句和New關鍵字實例化為具體對象後,才能做事情並佔用內存。其特點是:1 使用New關鍵字,可以創建任意數量的類的新實例,並且能夠將其存儲在Collection對象中。2 使用Property Let/Set/Get語句,可以編寫代碼驗證賦給類元素的值,並且可以編寫當值改變時執行的相應代碼。例如,能夠編寫代碼確保某個值為要求的特性。
  • Java之 Scanner類
    如果要輸入 int 或 float 類型的數據,在 Scanner 類中也有支持,但是在輸入之前最好先使用 hasNextXxx() 方法進行驗證,再使用 nextXxx() 來讀取:import java.util.Scanner;public class ScannerDemo
  • 硬核 | 使用spring cache讓我的接口性能瞬間提升了100倍
    當時考慮分類是放在商城首頁,以後流量大,而且不經常變動,為了提升首頁訪問速度,我考慮使用緩存。對於java開發而言,首先的緩存當然是redis。一般情況下從redis就都能獲取數據,因為相應的key是沒有設置過期時間的,數據會一直都存在。以防萬一,我們做了一次兜底,如果獲取不到數據,就會從mysql中獲取。本以為萬事大吉,後來,在系統上線之前,測試對商城首頁做了一次性能壓測,發現qps是100多,一直上不去。
  • MapStruct的使用,java實體類的轉換
    可能還會涉及到類型的轉換,如果這樣就會導致滿屏幕的get set方法,不僅感覺很low,而且不符合軟體的精神——不要重複造輪子。下面給大家推薦一款比較好用的插件,他可以用來完成實體類之間的轉換工作,並且可以自定義轉換方法-MapStruct。
  • 《梨泰院class》:只有我,才能定義真正的自我
    哪怕他真的能出示證件,表明自己的韓國公民身份。一看他黝黑的皮膚,普通人還是會覺得他就是個非洲來的,更不會承認他的韓國人身份。種族,皮膚,能界定一個人的身份;那麼血緣關係,為什麼不能算呢?很多影視作品,在早期引入「黑人群體身份認同」這個話題時,都只是以呈現他們備受壓迫剝削,社會地位低的方式,來表達這個群體身處困境;而現在,這個話題有了進一步的延伸:「當黑人有了其他的血統,或者他擁有了高雅技藝,出生在上流家庭,他還僅僅只是地位低下的黑人麼?」《梨泰院class》拋出的是這個問題的前半部內容,電影《綠皮書》,拋出的則是後半段。
  • 為你Springboot項目自定義一個通用的異常(實用乾貨)
    如果我們定義一個標準的異常處理體系。並在所有的服務中使用。那樣開發起來就可以快速定位。頁面也會更加的簡單和直觀。本文開發環境基於springboot2.4,IDE環境是IDEA。這裡從一個最簡單的異常案例。逐步過渡到完全自定義自己的異常。案例:Springboot查詢資料庫數據,發現返回的是null,就拋出異常。OK,基於這個思想,看一下實現的思路。
  • K8S的StorageClass實戰(NFS)
    >如果您已經準備好了kubernetes和NFS,咱們就開始實戰吧;如何創建StorageClass把創建StorageClass要做的的事情理清楚:創建namespace,這裡用hello-storageclass(您也可以選用自己喜歡的);
  • python-mock的幾點使用記錄
    mock非靜態方法使用Mock類,將目標方法賦值為Mock(return_value=XXXX),返回固定值默認情況下被mock的方法入參個數及賦值情況不會對返回值產生影響;校驗參數個數,再返回固定值, 需要用create_autospec
  • 類與對象的關係
    大家好,我是楊數Tos,這是《從零基礎到大神》系列課程的第72篇文章,第三階段的課程:Python進階知識:類與對象篇(三);定義類,產生對象,類中__init__方法的使用。一、在Python中定義類在Python中定義類的方法很簡單,只需使用關鍵字class進行類的聲明即可;格式:class 類名(父類【可選】):需要注意的是,類和函數有些不同的地方,在類中可以定義好__init__函數進行初始化設置