Java 16中的67個新功能

2021-03-27 18:02:48

探索Java 16提供的功能。

時光飛逝!即使在這樣的非凡時期,似乎也很難相信已經過去了六個月,現在我們有了JDK的新版本。像往常一樣,我將總結所有新功能,並對它們對Java應用程式開發的潛在影響進行一些評論。

並非所有版本都是相同的,並且取決於功能開發階段的對齊方式。一些將比其他具有更多的功能。 JDK 16包含了許多新內容,儘管其中一些是孵化器模組的延續或完成,或者是較早版本的預覽功能。我認為這是更快釋出節奏的最大好處之一。提供新功能而不使其成為標準的一部分,收集開發人員的反饋,並可能進行更改,這些都為JDK開發提供了改進的過程。

我將採用通常的方法將貼文分為語言更改,庫新增,與JVM相關的更新,以及其他所有內容。

Java語言

JDK 16中沒有新的語法構造,但是仍然存在一些重大變化。

JEP 394:instanceof的模式匹配

這是在JDK 14中作為預覽功能引入的,並在JDK 15中以預覽模式繼續進行。此功能已在JDK 16中完成。它包含在Java SE 16語言規範中,不需要–enable-preview 命令列標誌即可進行編譯和執行。

該功能在JDK 15中的工作方式有兩個小變化。首先是模式變數不再隱式最終。這是非常合乎邏輯的更改,因為未將區域性變數視為隱式最終變數。

第二個變化是現在是表示式模式instanceof的編譯時錯誤,該表示式範例將型別S的表示式與型別T的模式進行比較,其中S是T的子型別。這是一個例子:

var a = new ArrayList<String>();
if (a instanceof List l)

System.out.println(l.size());

這將導致編譯器錯誤:

Error: pattern type java.util.List is a subtype of expression type

java.util.ArrayList<java.lang.String>

這似乎是合乎邏輯的,因為if語句是多餘的,因為該謂詞始終會評估為true。

JEP 395:記錄

JDK 14中引入的另一個預覽功能現已在JDK 16中完成。對我來說,這是對Java語言的有益補充,因為我們終於有了一種處理元組的簡單方法。

JDK 15實現對Records的唯一更改是放寬了長期的限制,即內部類不能宣告顯式或隱式靜態的成員。這對於簡化某些流操作非常有用。通常,希望每個元素的流都通過一個以上的物件。現在,可以定義本地記錄,以減少原本會導致的型別汙染。

JEP 397:密封類

此功能是在JDK 15中作為預覽功能引入的,並在JDK 16中繼續作為預覽功能。

有三處更改:

新增新的語言語法通常需要新的關鍵字。將這些作為保留字新增到語言中(例如JDK
1.4中的assert)會嚴重影響向後相容性,因為這些字不再可用作識別符號。我們已經有了限制型別和限制關鍵字(例如var和seal)的概念來解決此問題。該JEP引入了上下文關鍵字的概念。具體來說,對於JDK
16,密封,非密封和允許是上下文關鍵字。
與匿名類和lambda表示式一樣,在確定密封類或介面的隱式宣告的允許子類時,區域性類可能不是密封類的子類。
JLS中變窄的參考轉換定義已擴充套件為在密封的層次結構中導航,以確定在編譯時無法進行哪些轉換。

類庫

通過各個JEP定義了四個新庫:

JEP 338:向量API

不要與collections API中不推薦使用的Vector類混淆,這是一個使開發人員能夠更好地使用基礎CPU向量功能的庫。所有現代處理器都具有單指令多資料(SIMD)功能。陣列的多個元素可以載入到非常寬的暫存器中,例如特定Intel處理器上的512位元AVX-512。例如,可以在單個機器指令週期內執行將每個值加10的單個操作,從而顯著提高數位密集型程式碼的效能。問題在於,編譯器需要識別可以轉換為向量運算的程式碼,這可能非常困難,尤其是在涉及條件運算的情況下。

向量API為開發人員提供了一種機制,可向編譯器明確表明應使用向量操作。但是,這確實使程式碼更加複雜。首先,有必要獲得一種載體。無論是要使用的暫存器大小還是要載入的資料型別(例如浮點數),這都是所需的向量形式。然後建立特定於型別的向量,並根據需要將其裝入值。最後一部分是使用適當的數學函數來操縱向量。

所有這些都可以在從JEP提起的範例中看到。

static final VectorSpecies<Float> SPECIES = FloatVector.SPECIES_256;
void vectorComputation(float[] a, float[] b, float[] c) {

for (int i = 0; i < a.length; i += SPECIES.length()) {
    var m = SPECIES.indexInRange(i, a.length);
    // FloatVector va, vb, vc;
    var va = FloatVector.fromArray(SPECIES, a, i, m);
    var vb = FloatVector.fromArray(SPECIES, b, i, m);
    var vc = va.mul(va)
       .add(vb.mul(vb))
       .neg();
    vc.intoArray(c, i, m);
}

}

它是作為孵化器模組提供的,因此在包含在Java SE標準中之前可能會有所更改。我希望開發人員將意識到使用縮寫是不必要的(或理想的)。我寧願看到mulple(),而不是mul(),依此類推。

JEP 380:UNIX域通訊端通道

UNIX域通訊端已經存在了很長時間,可追溯到1983年的BSD變體。但是,直到2018年,它們才在Windows平臺中引入。由於Java以跨平臺為榮,因此擁有一個無法在任何地方執行的庫沒有多大意義。

現在,我們有了UNIX域通訊端API,該API提供了一種在單個主機上處理程序間通訊的簡便方法。這些通訊端與使用TCP / IP環回非常相似,只不過它們是通過檔案系統路徑名而不是IP地址和埠號來定址的。UNIX域通訊端的優點是它們比回送更為有效和安全。

JEP 393:外部記憶體存取API

該庫在JDK 14中作為孵化器模組引入,在JDK 16中是其第三次迭代。

該API允許Java程式安全有效地存取Java堆外部的外部記憶體。它與Project Panama有關,該專案旨在取代Java本機介面,以使Java應用程式與本機庫進行互動。

JDK 15孵化器版本有四個更改:

MemorySegment和MemoryAddress介面之間的角色更加清晰地分離。

一個新的介面MemoryAccess,提供了常見的靜態記憶體存取器,以在簡單情況下將對VarHandle API的需求降至最低。

支援共用細分。

向清理器註冊段的能力

JEP 389:外部連結器API

作為孵化器模組,它引入了一個API,該API提供了對原生程式碼的靜態型別的純Java存取。這也是Project Panama的一部分,並且與Foreign Memory Access API結合使用,使繫結到本地庫變得更加簡單。最初的實現側重於用C編寫的庫。

首先必須在庫中找到適當的符號,才能與外來功能進行互動。LibraryLookup介面用於完成此操作,可以通過java.library.path上的庫或使用顯式檔案路徑從JVM中載入的符號中找到符號。

找到了要使用的外部函數之後,Clinker介面提供了兩種在Java程式碼和函數之間進行互動的方法:

downcallHandler方法返回一個MethodHandle,該方法可用於以與Java方法相同的方式呼叫外部函數。
upCallStub方法返回一個MemorySegment(來自外部記憶體存取API)。當外部函數需要回撥Java方法(通常作為回撥方法)時使用它。
當您檢視使用外部連結器API的範例時,程式碼似乎非常複雜。巴拿馬專案包含jextract工具,該工具尚未包含在JDK中。通過刪除所有樣板程式碼並允許簡單的靜態匯入來存取外部函數,這大大簡化了事情。希望這將使它成為JDK 17。

其他API變更

更新的Java SE規範中包括58個新的API元素,包括上述的UNIX域通訊端。以下是我認為對開發人員有趣的其他一些內容。

java.lang

在Class中,allowedSubClasses()已替換為getPermittedSubClasses()。

IndexOutOfBoundsException現在具有一個新的建構函式,該建構函式需要很長時間才能處理索引所參照的索引大於int的情況。

java.nio

ByteBuffer,CharBuffer,DoubleBuffer,FloatBuffer,IntBuffer,LongBuffer和ShortBuffer都有一個新的put()方法。這些方法從第二個緩衝區的給定偏移量開始複製第二個緩衝區的定義長度部分,到該方法被呼叫的緩衝區中的指定索引。該描述似乎很複雜,但是它是迴圈的簡單替代:

for (int i = offset, j = index; i < offset + length; i++, j++)

destinationBuffer.put(j, sourceBuffer.get(i));

java.time.format

DateTimeFormatterBuilder類具有一個新方法appendDayPeriodText()。這提供了一種將一天中的時間格式化為「上午」,「下午」等的方式,而不僅僅是AM或PM

javax.swing

很高興看到Swing仍在獲取更新,即使它們很小。

JPasswordField有一個新方法setText()。如果您想用已經記住的密碼欄位預先填充密碼欄位,這將很有用(這是我處理網站登入憑據的唯一方法)。
AccessibleJSlider有一個新的偵聽器stateChanged()
java.util.logging

LogRecord有兩個新方法,即getLongThreadID()和setLongThreadID()。這些是現在不建議使用的get / setThreadID方法的替代方法,該方法限於整數值。

JDK 16中的流API進行了一些激動人心的更改。

首先是在Stream介面上包含作為預設方法提供的新終端操作。這是toList(),它將流中的元素累積到不可修改的列表中。使列表不可修改已促使在Reddit之類的地方進行了一些討論,但這似乎是合乎邏輯的選擇。

第二個變化是引入mapMulti()作為中間操作,再次作為流上的一組預設方法提供(這包括針對double,int和long的特定於型別的方法)。當您第一次閱讀此操作的說明時,它看起來與flatMap相同。它返回一個流,該流包括用多個元素(特別是零個或多個元素)替換此流的每個元素的結果。與flatMap()有效地連線輸入流中每個元素生成的流不同,mapMulti()應用對映可以導致生成多個元素。如果要使用mapMulti()對映到零個或一個元素,則它與過濾操作相同。如果使用它僅對映到一個元素,則它將與map()相同。好處是當您要為每個輸入元素建立多個元素時。一個簡單的範例如下所示:

`wordList.stream()

.mapMulti((str, consumer) -> {
    for (int i = 0; I < str.length(); i++)
        consumer.accept(str.length());
    })
.forEach(System.out::print);`

對於單詞流,結果將是每個單詞的長度列印該次數。這裡重要的是,可以根據需要多次呼叫使用者上的accept()方法。

JDK 16(javax.tools.ToolProvider類別建構函式)中僅刪除了一個先前不推薦使用的API。

JVM相關功能

JEP 376將ZGC執行緒堆疊處理從安全點移至並行階段,即使在大型堆上,也可以大大減少GC安全點內部的暫停。ZGC的初始階段(與許多收集器一樣)建立了應用程式可存取的物件的根集合。為此,需要遍歷堆疊以查詢物件參照。過去,這要求所有執行緒都達到全域性安全點,在該點上,所有執行緒都可以安全地暫停並完成工作。在ZGC中,這不再是必需的,可以與應用程式執行緒同時完成。有趣的是,此設計方式並未將其繫結到ZGC,因此Shenandoah專案也選擇使用此功能。

元空間是永久代的替代,永久代曾經包含在堆中。 JEP 387引入了彈性元空間,其中使用的記憶體可以更迅速地返回作業系統。該程式碼也得到了簡化,從而減少了維護開銷。

其他JDK功能

多年來,OpenJDK專案基礎結構最重大的變化是從Mercurial到Git的OpenJDK原始碼管理系統(JEP 357)遷移,並將所有程式碼託管在Github(JEP 369)上。Git / Github在開發人員中非常受歡迎,因此這是一個明智的決定,它將幫助吸引更多的人蔘與該專案。

與JVM原始碼也特別相關的是允許使用C ++ 14功能(JEP 347)的舉動。似乎使用更新的語言功能確實步履蹣跚,因為我們現在才包括使用已有七年曆史的功能。想象一下這會給JVM開發人員帶來的興奮,他們現在可以使用二進位制文字了!

JDK有兩個新的埠,從JDK 16開始:

高山Linux(JEP 386):這主要針對希望部署基於Java的微服務的使用者。Alpine
Linux使用C庫的Musl實現來提供最少的Linux環境。結合使用jlink生成的JDK,Docker映像可小至38Mb。誠然,這僅包括java.base模組,但這是一個很好的起點。
Windows / AArch64(JEP 388):Arm
64處理器在伺服器端和使用者端都變得越來越流行。此埠將有助於進一步提高Java的流行度。從一開始就參與Java,我必須說,很高興看到Microsoft為OpenJDK專案做出了貢獻!

Valhalla專案希望將值型別帶入Java,在交付Java之前,需要對Java進行一些細微的更改。現在將現有的原始包裝器類(例如Integer)指定為基於值的類,並且不建議使用建構函式將其刪除。通常,無論如何都不會使用它們,但是如果您使用了它們,則會發出適當的編譯器警告。如果嘗試在這些類的範例上進行同步,還將生成警告訊息。JEP 390中對此進行了詳細說明。

JDK 9引入了Java Platform Module System,其中一個必然的部分是JEP 260,它封裝了JDK的大多數內部API。因為這有可能破壞許多現有程式碼,所以包含了一個命令列標誌:–illegal-access,它可以具有以下四個值之一:allow,warn,debug和deny。從JDK 9開始,預設值是允許的,從而允許使用關鍵內部API的程式碼繼續按預期執行。在JDK 16中,感謝JEP 396,預設情況下,JDK內部API嚴格封裝(將標誌值更改為拒絕)。JEP宣告這將不會封裝尚不存在標準替代品的API。這意味著sun.misc.Unsafe將保持可用。現在,一些在JDK 15中生成警告的反射程式碼將引發異常並終止,因此請小心!

儘管尚未得到確認,但在JDK 17中似乎不再可能放鬆封裝,從而有效地刪除了–非法存取標誌。

包裝工具

在JDK 11之前,Oracle將JavaFX包含在其JDK構建中。這提供了一個不錯的工具,可以為Java應用程式生成可安裝的,特定於平臺的軟體包。刪除此功能後,開發人員表示希望恢復此功能。它作為孵化器模組包含在JDK 14中(儘管不是確切的模組,但孵化器也涵蓋了工具)。現在,它已作為完整功能包含在JDK 16中。與JDK 14相比,唯一的變化是將–bind-services命令列標誌替換為更通用的–jlink-options標誌。

JDK中刪除了兩個實驗功能,特別是Graal JIT編譯器和提前工具jaot。據推測,這是因為Oracle正在投資並推廣包括此功能的GraalVM。

結論

JDK 16繼續了Java平臺的快速變更步伐。我們真的開始看到預覽功能和孵化器模組的功能和優勢。

使用JDK 17,我們將看到下一個長期支援版本。儘管這不是OpenJDK的概念,但它是所有OpenJDK二進位制發行版(包括Azul的Zulu)提供程式所提供的。因此,您絕對應該使用JDK 16測試您的應用程式,以檢視這些更改是否有任何影響。

您準備好使用現代Java了嗎?

(注意:我計算了67個新功能和API,其中包括17個JEP和50個新Java SE API元素。UNIX域通訊端佔8個API元素)。

參考: 《2020最新Java基礎精講視訊教學和學習路線!》
連結:https://blog.csdn.net/weixin_...