2021 年 9 月怎抛,Oracle 發(fā)布了 Java 17偿乖,Java 的下一個(gè)長(zhǎng)期支持版本矗钟。如果你在使用 Java 8 或 Java 11新症,可能不會(huì)注意到 Java 12 之后新增的一些很酷的新特性恳蹲。
因?yàn)檫@是一個(gè)很重要的版本虐块,我會(huì)突出介紹一些我個(gè)人很感興趣的新特性!
需要注意的是嘉蕾,Java 中的大多數(shù)變更首先需要經(jīng)過(guò)“預(yù)覽”階段贺奠,也就是說(shuō)它們被添加到一個(gè)版本中,但還沒(méi)有完成错忱。人們可以嘗試使用它們儡率,但不建議將其用在生產(chǎn)環(huán)境中。
這里所列舉的所有特性都已正式添加到 Java 中以清,并且已經(jīng)過(guò)了預(yù)覽階段儿普。
1:封印類(lèi)
在 Java 15 中處于預(yù)覽階段并在 Java 17 中成為正式特性的封印類(lèi)?,提供了一種新的繼承規(guī)則限定方法掷倔。當(dāng)你在類(lèi)或接口前面添加 sealed 關(guān)鍵字的同時(shí)眉孩,也添加了一個(gè)允許擴(kuò)展這個(gè)類(lèi)或?qū)崿F(xiàn)這個(gè)接口的類(lèi)的清單。例如,如果你定義了一個(gè)類(lèi):
public abstract sealed class?Color?permits?Red, Blue, Yellow
復(fù)制代碼
也就是說(shuō)浪汪,只有 Red障贸、Blue 和 Yellow 可以繼承這個(gè)類(lèi),其他類(lèi)想要繼承它都無(wú)法通過(guò)編譯吟宦。
你也可以不使用 permits 關(guān)鍵字篮洁,然后將類(lèi)定義與類(lèi)放在相同的文件中,如下所示:
public abstract sealed class?Color?{...}
復(fù)制代碼
注意殃姓,這些子類(lèi)并不是嵌套在封印類(lèi)中袁波,而是放在類(lèi)定義之后。這與使用關(guān)鍵字 permit 是一樣的效果蜗侈,可以擴(kuò)展 Color 的類(lèi)只有 Red篷牌、Blue 和 Yellow。
那么踏幻,封印類(lèi)通常用在哪里枷颊?通過(guò)限定繼承規(guī)則,同時(shí)也限定了封裝規(guī)則该面。假設(shè)你正在開(kāi)發(fā)一個(gè)庫(kù)夭苗,并且需要將抽象類(lèi) Color 包含在其中。你知道 Color 這個(gè)類(lèi)以及哪些類(lèi)需要擴(kuò)展它隔缀,但如果它被聲明為 public 的题造,那么你有什么辦法可以阻止外部代碼擴(kuò)展它?
如果有人誤解了它的用途并用 Square 對(duì)它進(jìn)行了擴(kuò)展猾瘸,該怎么辦界赔?這符合你的意圖嗎?或者你其實(shí)是想讓 Color 保持私有牵触?但即使是這樣淮悼,包級(jí)別的可見(jiàn)性也不能避免所有問(wèn)題。如果后來(lái)有人對(duì)這個(gè)庫(kù)進(jìn)行了擴(kuò)展了該怎么辦揽思?他們?nèi)绾文軌蛑滥阒淮蛩阕屢恍〔糠诸?lèi)集成 Color袜腥?
封印類(lèi)不僅可以保護(hù)你的代碼不受外部代碼的影響,還是一種向你可能從未見(jiàn)過(guò)的人傳達(dá)意圖的方式绰更。如果一個(gè)類(lèi)是封印的瞧挤,你是在傳達(dá)只有某些類(lèi)可以擴(kuò)展它。這種健壯性可以確保在多年以后任何閱讀你代碼的人都會(huì)理解代碼的嚴(yán)謹(jǐn)儡湾。
2:增強(qiáng)的空指針異常
增強(qiáng)的空指針異常?是一個(gè)有趣的更新——不會(huì)太復(fù)雜特恬,但仍然很受歡迎。這個(gè)增強(qiáng)在 Java 14 中正式發(fā)布徐钠,提高了空指針異常(NullPointerException癌刽,簡(jiǎn)稱(chēng) NPE)的可讀性,可以打印出在拋出異常位置所調(diào)用的方法的名稱(chēng)和空變量的名稱(chēng)。例如显拜,如果你調(diào)用 a.b.getName()衡奥,而 b 為空,那么異常的堆棧跟蹤信息會(huì)告訴你調(diào)用 getName()失敗远荠,因?yàn)?b 是空的矮固。
我們都知道,NPE 是一種非常常見(jiàn)的異常譬淳,雖然在大多數(shù)情況下找出導(dǎo)致拋出異常的根源并不難档址,但你會(huì)時(shí)不時(shí)地遇到同時(shí)有兩三個(gè)可疑變量的情況。你進(jìn)入調(diào)試模式邻梆,開(kāi)始查看代碼守伸,但問(wèn)題很難重現(xiàn)。你只能試著回憶最初做了什么導(dǎo)致拋出 NPE 的浦妄。
如果你能提前獲得這些信息尼摹,就不用這些麻煩地調(diào)試了。這就是這個(gè)特性的閃光點(diǎn):不用再猜測(cè) NPE 是從哪里拋出來(lái)的剂娄。在關(guān)鍵時(shí)刻蠢涝,當(dāng)你遇到難以重現(xiàn)的異常場(chǎng)景時(shí),你就有了解決問(wèn)題所需的一切宜咒。
這絕對(duì)是個(gè)救星惠赫!
3:switch 表達(dá)式
希望你耐心聽(tīng)我說(shuō)幾句——switch表達(dá)式?(在 Java 12 中預(yù)覽,并正式添加到 Java 14 中)是 switch 語(yǔ)句和 lambda 之間的某種結(jié)合故黑。真的,當(dāng)我第一次向別人描述 switch 表達(dá)式時(shí)庭砍,我的說(shuō)法是他們把 switch 語(yǔ)句 lambda 化了场晶。請(qǐng)看下面這個(gè)語(yǔ)法:
String adjacentColor = switch (color) {
復(fù)制代碼
現(xiàn)在明白我的意思了嗎?
一個(gè)明顯的區(qū)別是沒(méi)有了 break 語(yǔ)句怠缸。switch 表達(dá)式延續(xù)了 Oracle 讓 Java 語(yǔ)法更簡(jiǎn)潔的趨勢(shì)诗轻。Oracle 非常討厭大多數(shù) switch 語(yǔ)句包含很多的 CASE BREAK、CASE BREAK揭北、CASE BREAK……扳炬。
老實(shí)說(shuō),他們討厭這個(gè)是對(duì)的搔体,因?yàn)槿藗兒苋菀自谶@個(gè)地方犯錯(cuò)恨樟。我們當(dāng)中是否有人敢說(shuō)他們從來(lái)沒(méi)有遇到過(guò)這種情況:忘記在 switch 里添加 break 語(yǔ)句,只有當(dāng)代碼在運(yùn)行時(shí)發(fā)生崩潰才知道疚俱?switch 表達(dá)式通過(guò)一種有趣的方式修復(fù)了這個(gè)問(wèn)題劝术,你只需要用逗號(hào)隔開(kāi)同一個(gè)代碼塊里所有的值。沒(méi)錯(cuò),不需要使用 break 了养晋!它會(huì)替你處理好衬吆!
switch 表達(dá)式還新增了 yield 關(guān)鍵字。如果一個(gè) case 進(jìn)入了一個(gè)代碼塊绳泉,yield 將被作為 switch 表達(dá)式的返回語(yǔ)句逊抡。例如,如果我們將上面的代碼稍作修改:
String adjacentColor = switch (color) {
復(fù)制代碼
在默認(rèn) case 里零酪,System.out.println()方法將被執(zhí)行冒嫡,adjacentColor 變量最終的值是“Unknown Color”,因?yàn)檫@是 yield 返回的結(jié)果蛾娶。
總的來(lái)說(shuō)灯谣,switch 表達(dá)式是一種更簡(jiǎn)潔的 switch 語(yǔ)句,但它不會(huì)取代 switch 語(yǔ)句蛔琅,這兩種語(yǔ)句都可用胎许。
4:文本塊
文本塊?特性在 Java 13 中預(yù)覽,并正式添加到 Java 15 中罗售,它可以簡(jiǎn)化多行字符串的寫(xiě)法辜窑,支持換行,并在不需要轉(zhuǎn)義字符的情況下保持縮進(jìn)寨躁。要?jiǎng)?chuàng)建一個(gè)文本塊穆碎,只需要這樣:
String text = """
復(fù)制代碼
注意,這個(gè)變量仍然是一個(gè)字符串职恳,只是它隱含了換行和制表符所禀。同樣,如果我們想要使用引號(hào)放钦,也不需要轉(zhuǎn)義字符:
String text = """
復(fù)制代碼
唯一需要使用反斜杠轉(zhuǎn)義字符的地方是當(dāng)你想要在文本塊里包含""":
String text = """
復(fù)制代碼
除此之外色徘,你可以調(diào)用 String 的 format()方法,用動(dòng)態(tài)內(nèi)容替換文本塊中的占位符:
String name = "Chris";
復(fù)制代碼
每行后面的空格都會(huì)被剪切掉操禀,除非你指定了'\s'褂策,這是文本塊的一個(gè)轉(zhuǎn)義字符:
String text1 = """
復(fù)制代碼
那么,在什么情況下會(huì)使用文本塊呢颓屑?除了能夠?qū)Υ髩K的文本進(jìn)行格式化外斤寂,將代碼片段粘貼到字符串中也變得非常容易。因?yàn)榭s進(jìn)被保留了揪惦,如果你要寫(xiě)一個(gè) HTML 或 Python 代碼塊遍搞,或使用其他任何語(yǔ)言,你都可以按照正常的方式寫(xiě)好它們丹擎,然后用"""把它們括起來(lái)尾抑,就可以保留代碼的格式歇父。你甚至可以用文本塊來(lái)編寫(xiě) JSON,并使用 format()方法輕松地插入值再愈。
總的來(lái)說(shuō)榜苫,這是個(gè)一個(gè)很方便的特性。雖然文本塊看起來(lái)只是一個(gè)小功能翎冲,但從長(zhǎng)遠(yuǎn)來(lái)看垂睬,類(lèi)似這種可以提升開(kāi)發(fā)效率的小功能會(huì)逐漸增加。
5:record 類(lèi)
record類(lèi)?在 Java 14 中預(yù)覽抗悍,并正式添加到 Java 16 中驹饺,是一種數(shù)據(jù)類(lèi),處理所有與 POJO 相關(guān)的樣板代碼缴渊。也就是說(shuō)赏壹,如果你聲明了一個(gè) record 類(lèi):
public record?Coord(int x, int y) {
復(fù)制代碼
equals()和 hashcode()方法會(huì)自動(dòng)實(shí)現(xiàn),toString()將返回這個(gè)類(lèi)實(shí)例包含的所有字段的值衔沼,最重要的是蝌借,x()和 y()將分別返回 x 和 y 的值。想想你之前寫(xiě)過(guò)的 POJO 類(lèi)指蚁,并想象一下用 record 類(lèi)來(lái)代替它們會(huì)怎樣菩佑。是不是好看多了?省了多少事了凝化?
除此之外稍坯,record 類(lèi)是 final 和不可變的——不能被繼承,并且類(lèi)實(shí)例一旦被創(chuàng)建搓劫,它的字段就不能被修改瞧哟。你可以在 record 類(lèi)中聲明方法,包括非靜態(tài)方法和靜態(tài)方法:
public record?Coord(int x, int y) {
復(fù)制代碼
record 類(lèi)可以有多個(gè)構(gòu)造器:
public record?Coord(int x, int y) {
復(fù)制代碼
需要注意的是枪向,當(dāng)你在 record 類(lèi)中聲明自定義構(gòu)造函數(shù)時(shí)绢涡,必須調(diào)用默認(rèn)構(gòu)造函數(shù)。否則遣疯,record 類(lèi)將不知道如何處理它的值。如果你聲明了一個(gè)與默認(rèn)構(gòu)造函數(shù)一樣的構(gòu)造函數(shù)凿傅,你要初始化所有的字段:
public record?Coord(int x, int y) {
復(fù)制代碼
關(guān)于 record 類(lèi)缠犀,有很多可討論的話(huà)題。這是一個(gè)大的變更聪舒,在合適的地方使用它們辨液,它們會(huì)非常有用。我在這里沒(méi)有涵蓋所有內(nèi)容箱残,但希望這能讓你了解它們所提供的能力滔迈。
6:模式匹配
模式匹配?是 Oracle 在與 Java 冗長(zhǎng)語(yǔ)法的斗爭(zhēng)中做出的另一個(gè)舉措止吁。模式匹配在 Java 14 和 Java 15 中預(yù)覽過(guò),并正式添加到 Java 16 中燎悍,它可以在 instanceof 條件得到滿(mǎn)足后消除不必要的類(lèi)型轉(zhuǎn)換敬惦。例如,我們都很熟悉這樣的代碼:
if (o instanceof Car) {
復(fù)制代碼
如果你想要訪(fǎng)問(wèn) Car 的方法谈山,必要要這么做俄删。在第二行,o 是 Car 的實(shí)例奏路,這是毫無(wú)疑問(wèn)的畴椰,instanceof 已經(jīng)確認(rèn)了這一點(diǎn)。如果我們使用模式匹配鸽粉,只要做一個(gè)小小的改變:
if (o instanceof Car c) {
復(fù)制代碼
現(xiàn)在斜脂,所有的對(duì)象類(lèi)型轉(zhuǎn)換都由編譯器完成〈セ看起來(lái)改變很小帚戳,但它避免了很多樣板代碼。這也適用于條件分支威兜,當(dāng)你進(jìn)入一個(gè)已經(jīng)明確了對(duì)象類(lèi)型的分支:
if (!(o instance of Car c)) {
復(fù)制代碼
你甚至可以在 instanceof 那一行使用模式匹配:
public boolean?isHonda(Object o) {
復(fù)制代碼
雖然模式匹配不像其他一些變更那么大销斟,但還是簡(jiǎn)化了常用的代碼。
Java 17 將繼續(xù)演進(jìn)
當(dāng)然椒舵,Java 12 到 Java 17 并不是只推出了這些更新蚂踊,這些只是我認(rèn)為比較有趣的部分。用最新的 Java 版本來(lái)運(yùn)行大型項(xiàng)目需要很大的勇氣笔宿,如果是從 Java 8 遷移過(guò)來(lái)犁钟,則更需要勇氣。
如果有人猶豫不決泼橘,是可以理解的涝动。但是,即使你沒(méi)有遷移計(jì)劃炬灭,或者某個(gè)升級(jí)計(jì)劃可能持續(xù)數(shù)年之久醋粟,跟上語(yǔ)言新特性的變化總歸是件好事。我希望我分享的這些內(nèi)容能夠讓它們更加深入人心重归,讓閱讀過(guò)這些內(nèi)容的人都可以開(kāi)始考慮如何使用它們米愿!
Java 17 很特別——它是下一個(gè)長(zhǎng)期支持版本,接過(guò)了 Java 11 的接力棒鼻吮,并且很可能在未來(lái)幾年內(nèi)成為遷移最多的 Java 版本育苟。即使你現(xiàn)在還沒(méi)有做好準(zhǔn)備,可以開(kāi)始學(xué)習(xí)起來(lái)了椎木,當(dāng)你身處基于 Java 17 的項(xiàng)目當(dāng)中违柏,你已經(jīng)是一名經(jīng)驗(yàn)豐富的開(kāi)發(fā)者博烂!