天天看點

從 Java 12到Java 17 那些激動人心的新特性

作者 | Christopher Bielak

譯者 | 屠靈

策劃 | 丁曉昀

2021 年 9 月,Oracle 釋出了 Java 17,Java 的下一個長期支援版本。如果你在使用 Java 8 或 Java 11,可能不會注意到 Java 12 之後新增的一些很酷的新特性。

因為這是一個很重要的版本,我會突出介紹一些我個人很感興趣的新特性!

需要注意的是,Java 中的大多數變更首先需要經過“預覽”階段,也就是說它們被添加到一個版本中,但還沒有完成。人們可以嘗試使用它們,但不建議将其用在生産環境中。

這裡所列舉的所有特性都已正式添加到 Java 中,并且已經過了預覽階段。

1:封印類

在 Java 15 中處于預覽階段并在 Java 17 中成為正式特性的 封印類,提供了一種新的繼承規則限定方法。當你在類或接口前面添加 sealed 關鍵字的同時,也添加了一個允許擴充這個類或實作這個接口的類的清單。例如,如果你定義了一個類:

也就是說,隻有 Red、Blue 和 Yellow 可以繼承這個類,其他類想要繼承它都無法通過編譯。

你也可以不使用 permits 關鍵字,然後将類定義與類放在相同的檔案中,如下所示:

注意,這些子類并不是嵌套在封印類中,而是放在類定義之後。這與使用關鍵字 permit 是一樣的效果,可以擴充 Color 的類隻有 Red、Blue 和 Yellow。

那麼,封印類通常用在哪裡?通過限定繼承規則,同時也限定了封裝規則。假設你正在開發一個庫,并且需要将抽象類 Color 包含在其中。你知道 Color 這個類以及哪些類需要擴充它,但如果它被聲明為 public 的,那麼你有什麼辦法可以阻止外部代碼擴充它?

如果有人誤解了它的用途并用 Square 對它進行了擴充,該怎麼辦?這符合你的意圖嗎?或者你其實是想讓 Color 保持私有?但即使是這樣,包級别的可見性也不能避免所有問題。如果後來有人對這個庫進行了擴充了該怎麼辦?他們如何能夠知道你隻打算讓一小部分類內建 Color?

封印類不僅可以保護你的代碼不受外部代碼的影響,還是一種向你可能從未見過的人傳達意圖的方式。如果一個類是封印的,你是在傳達隻有某些類可以擴充它。這種健壯性可以確定在多年以後任何閱讀你代碼的人都會了解代碼的嚴謹。

2:增強的空指針異常

增強的 空指針異常 是一個有趣的更新——不會太複雜,但仍然很受歡迎。這個增強在 Java 14 中正式釋出,提高了空指針異常 (NullPointerException,簡稱 NPE) 的可讀性,可以列印出在抛出異常位置所調用的方法的名稱和空變量的名稱。例如,如果你調用 a.b.getName(),而 b 為空,那麼異常的堆棧跟蹤資訊會告訴你調用 getName() 失敗,因為 b 是空的。

我們都知道,NPE 是一種非常常見的異常,雖然在大多數情況下找出導緻抛出異常的根源并不難,但你會時不時地遇到同時有兩三個可疑變量的情況。你進入調試模式,開始檢視代碼,但問題很難重制。你隻能試着回憶最初做了什麼導緻抛出 NPE 的。

如果你能提前獲得這些資訊,就不用這些麻煩地調試了。這就是這個特性的閃光點:不用再猜測 NPE 是從哪裡抛出來的。在關鍵時刻,當你遇到難以重制的異常場景時,你就有了解決問題所需的一切。

這絕對是個救星!

3:switch 表達式

希望你耐心聽我說幾句——switch 表達式(在 Java 12 中預覽,并正式添加到 Java 14 中) 是 switch 語句和 lambda 之間的某種結合。真的,當我第一次向别人描述 switch 表達式時,我的說法是他們把 switch 語句 lambda 化了。請看下面這個文法:

現在明白我的意思了嗎?

一個明顯的差別是沒有了 break 語句。switch 表達式延續了 Oracle 讓 Java 文法更簡潔的趨勢。Oracle 非常讨厭大多數 switch 語句包含很多的 CASE BREAK、CASE BREAK、CASE BREAK……。

老實說,他們讨厭這個是對的,因為人們很容易在這個地方犯錯。我們當中是否有人敢說他們從來沒有遇到過這種情況:忘記在 switch 裡添加 break 語句,隻有當代碼在運作時發生崩潰才知道?switch 表達式通過一種有趣的方式修複了這個問題,你隻需要用逗号隔開同一個代碼塊裡所有的值。沒錯,不需要使用 break 了!它會替你處理好!

switch 表達式還新增了 yield 關鍵字。如果一個 case 進入了一個代碼塊,yield 将被作為 switch 表達式的傳回語句。例如,如果我們将上面的代碼稍作修改:

總的來說,switch 表達式是一種更簡潔的 switch 語句,但它不會取代 switch 語句,這兩種語句都可用。

4:文本塊

文本塊 特性在 Java 13 中預覽,并正式添加到 Java 15 中,它可以簡化多行字元串的寫法,支援換行,并在不需要轉義字元的情況下保持縮進。要建立一個文本塊,隻需要這樣:

注意,這個變量仍然是一個字元串,隻是它隐含了換行和制表符。同樣,如果我們想要使用引号,也不需要轉義字元:

唯一需要使用反斜杠轉義字元的地方是當你想要在文本塊裡包含""":

除此之外,你可以調用 String 的 format() 方法,用動态内容替換文本塊中的占位符:

每行後面的空格都會被剪切掉,除非你指定了'\s',這是文本塊的一個轉義字元:

那麼,在什麼情況下會使用文本塊呢?除了能夠對大塊的文本進行格式化外,将代碼片段粘貼到字元串中也變得非常容易。因為縮進被保留了,如果你要寫一個 HTML 或 Python 代碼塊,或使用其他任何語言,你都可以按照正常的方式寫好它們,然後用"""把它們括起來,就可以保留代碼的格式。你甚至可以用文本塊來編寫 JSON,并使用 format() 方法輕松地插入值。

總的來說,這是個一個很友善的特性。雖然文本塊看起來隻是一個小功能,但從長遠來看,類似這種可以提升開發效率的小功能會逐漸增加。

5:record 類

record 類 在 Java 14 中預覽,并正式添加到 Java 16 中,是一種資料類,處理所有與 POJO 相關的樣闆代碼。也就是說,如果你聲明了一個 record 類:

equals() 和 hashcode() 方法會自動實作,toString() 将傳回這個類執行個體包含的所有字段的值,最重要的是,x() 和 y() 将分别傳回 x 和 y 的值。想想你之前寫過的 POJO 類,并想象一下用 record 類來代替它們會怎樣。是不是好看多了?省了多少事了?

除此之外,record 類是 final 和不可變的——不能被繼承,并且類執行個體一旦被建立,它的字段就不能被修改。你可以在 record 類中聲明方法,包括非靜态方法和靜态方法:

record 類可以有多個構造器:

需要注意的是,當你在 record 類中聲明自定義構造函數時,必須調用預設構造函數。否則,record 類将不知道如何處理它的值。如果你聲明了一個與預設構造函數一樣的構造函數,你要初始化所有的字段:

關于 record 類,有很多可讨論的話題。這是一個大的變更,在合适的地方使用它們,它們會非常有用。我在這裡沒有涵蓋所有内容,但希望這能讓你了解它們所提供的能力。

6:模式比對

模式比對 是 Oracle 在與 Java 冗長文法的鬥争中做出的另一個舉措。模式比對在 Java 14 和 Java 15 中預覽過,并正式添加到 Java 16 中,它可以在 instanceof 條件得到滿足後消除不必要的類型轉換。例如,我們都很熟悉這樣的代碼:

如果你想要通路 Car 的方法,必要要這麼做。在第二行,o 是 Car 的執行個體,這是毫無疑問的,instanceof 已經确認了這一點。如果我們使用模式比對,隻要做一個小小的改變:

現在,所有的對象類型轉換都由編譯器完成。看起來改變很小,但它避免了很多樣闆代碼。這也适用于條件分支,當你進入一個已經明确了對象類型的分支:

你甚至可以在 instanceof 那一行使用模式比對:

雖然模式比對不像其他一些變更那麼大,但還是簡化了常用的代碼。

Java 17 将繼續演進

當然,Java 12 到 Java 17 并不是隻推出了這些更新,這些隻是我認為比較有趣的部分。用最新的 Java 版本來運作大型項目需要很大的勇氣,如果是從 Java 8 遷移過來,則更需要勇氣。

如果有人猶豫不決,是可以了解的。但是,即使你沒有遷移計劃,或者某個更新計劃可能持續數年之久,跟上語言新特性的變化總歸是件好事。我希望我分享的這些内容能夠讓它們更加深入人心,讓閱讀過這些内容的人都可以開始考慮如何使用它們!

Java 17 很特别——它是下一個長期支援版本,接過了 Java 11 的接力棒,并且很可能在未來幾年内成為遷移最多的 Java 版本。即使你現在還沒有做好準備,可以開始學習起來了,當你身處基于 Java 17 的項目當中,你已經是一名經驗豐富的開發者!

作者簡介:

Christopher Bielak是Saggezza(被 Infostretch 收購的一家公司) 的 Java 開發人員,他對軟體開發語言有濃厚的興趣。他在銀行和金融領域有 10 年的工作經驗,倡導向更尖端的技術邁進。

https://www.infoq.com/articles/six-features-jdk12-to-jdk17/

繼續閱讀