Java22重磅發(fā)布?。?!卷不動了,真的卷不動了......
就在3月19日,Java22重磅發(fā)布。Java22新增了12項增強(qiáng)功能,其中包括七個預(yù)覽特性和一個孵化器特性,這些功能都顯著到足以引起JDK增強(qiáng)提案(JEPs)的關(guān)注。它們涵蓋了Java語言、其API、性能以及JDK中包含的工具的改進(jìn)。
真的卷不動了,,前段時間才將項目升級到Java17...
接下來我們看看具體的新特性介紹...
Java語言上的改進(jìn)
Unnamed Variables & Patterns - JEP 456
匿名變量和模式。當(dāng)需要但未使用變量聲明或嵌套模式時,提高了可讀性。這兩者都用下劃線字符表示。
優(yōu)化:
- 捕獲開發(fā)人員意圖,即給定的綁定或Lambda參數(shù)未使用,并強(qiáng)制執(zhí)行該屬性以澄清程序并減少錯誤的機(jī)會。
比如我們可以在循環(huán)中這樣使用:
static int count(Iterable<Order> orders) {
int total = 0;
for (Order _ : orders) // Unnamed variable
total++;
return total;
}
或者:
for (int i = 0, _ = sideEffect(); i < 10; i++) { ... i ... }
或者while
循環(huán):
while (q.size() >= 3) {
var x = q.remove();
var _ = q.remove(); // Unnamed variable
var _ = q.remove(); // Unnamed variable
... new Point(x, 0) ...
}
- 通過識別必須聲明但未使用的變量(例如,在捕獲子句中)來提高所有代碼的可維護(hù)性。
String s = ...
try {
int i = Integer.parseInt(s);
... i ...
} catch (NumberFormatException _) { // Unnamed variable
System.out.println("Bad number: " + s);
}
多個catch
:
try { ... }
catch (Exception _) { ... } // Unnamed variable
catch (Throwable _) { ... } // Unnamed variable
或者這樣使用try...resource
try (var _ = ScopedContext.acquire()) { // Unnamed variable
... no use of acquired resource ...
}
在lamba中我們可以這樣使用:
...stream.collect(Collectors.toMap(String::toUpperCase,
_ -> "NODATA")) // Unnamed variable
- 允許在單個 case 標(biāo)簽中出現(xiàn)多個模式,如果它們都沒有聲明任何模式變量。
例如:
switch (ball) {
case RedBall _ -> process(ball); // Unnamed pattern variable
case BlueBall _ -> process(ball); // Unnamed pattern variable
case GreenBall _ -> stopProcessing(); // Unnamed pattern variable
}
或者:
switch (box) {
case Box(RedBall _) -> processBox(box); // Unnamed pattern variable
case Box(BlueBall _) -> processBox(box); // Unnamed pattern variable
case Box(GreenBall _) -> stopProcessing(); // Unnamed pattern variable
case Box(var _) -> pickAnotherBox(); // Unnamed pattern variable
}
通過這種改進(jìn)允許我們省略名稱,未命名的模式變量使得基于類型模式的運行時數(shù)據(jù)探索在switch語句塊以及使用instanceof運算符時,視覺上更加清晰明了。
- 通過省略不必要的嵌套類型模式來改善記錄模式的可讀性。
Statements before super - JEP 447
構(gòu)造器中的前置語句。在構(gòu)造函數(shù)中,允許在顯式構(gòu)造函數(shù)調(diào)用之前出現(xiàn)不引用正在創(chuàng)建的實例的語句。
優(yōu)化:
- 為開發(fā)人員提供更大的自由度來表達(dá)構(gòu)造函數(shù)的行為,從而使當(dāng)前必須因子化為輔助靜態(tài)方法、輔助中間構(gòu)造函數(shù)或構(gòu)造函數(shù)參數(shù)的邏輯能夠更自然地放置。
有時我們需要驗證傳遞給超類構(gòu)造函數(shù)的參數(shù)。雖然我們可以在事后進(jìn)行參數(shù)驗證,但這意味著可能會進(jìn)行不必要的操作。例如如下:
public class PositiveBigInteger extends BigInteger {
public PositiveBigInteger(long value) {
super(value); // Potentially unnecessary work
if (value <= 0)
throw new IllegalArgumentException("non-positive value");
}
}
Java22中的做法是聲明一個能夠快速失敗的構(gòu)造函數(shù),即在調(diào)用超類構(gòu)造函數(shù)之前先驗證其參數(shù)。目前我們只能采用內(nèi)聯(lián)方式實現(xiàn)這一點,即借助于輔助靜態(tài)方法:
public class PositiveBigInteger extends BigInteger {
public PositiveBigInteger(long value) {
super(verifyPositive(value));
}
private static long verifyPositive(long value) {
if (value <= 0)
throw new IllegalArgumentException("non-positive value");
return value;
}
}
我們還可以將驗證邏輯直接包含在構(gòu)造函數(shù)內(nèi)部,這段代碼將會更具可讀性。
public class PositiveBigInteger extends BigInteger {
public PositiveBigInteger(long value) {
if (value <= 0)
throw new IllegalArgumentException("non-positive value");
super(value);
}
}
- 保留了構(gòu)造函數(shù)在類實例化期間按自上而下順序運行的現(xiàn)有保證,確保子類構(gòu)造函數(shù)中的代碼不能干擾超類實例化。
為了給超類構(gòu)造函數(shù)提供參數(shù),我們必須執(zhí)行另外的計算,再次不得不借助于輔助方法:
public class Sub extends Super {
public Sub(Certificate certificate) {
super(prepareByteArray(certificate));
}
// 輔助方法
private static byte[] prepareByteArray(Certificate certificate) {
var publicKey = certificate.getPublicKey();
if (publicKey == null)
throw new IllegalArgumentException("null certificate");
return switch (publicKey) {
case RSAKey rsaKey -> ...
case DSAPublicKey dsaKey -> ...
...
default -> ...
};
}
}
超類構(gòu)造函數(shù)接受一個字節(jié)數(shù)組作為參數(shù),而子類構(gòu)造函數(shù)接受一個Certificate
對象作為參數(shù)。為了滿足超類構(gòu)造函數(shù)調(diào)用必須為子類構(gòu)造函數(shù)中的第一條語句這一限制,我們聲明了一個輔助方法prepareByteArray
來為此調(diào)用準(zhǔn)備參數(shù)。
如果能夠?qū)?shù)準(zhǔn)備代碼直接嵌入到構(gòu)造函數(shù)中,這段代碼會更具可讀性。在Java22中我們可以這么做:
public Sub(Certificate certificate) {
var publicKey = certificate.getPublicKey();
if (publicKey == null)
throw new IllegalArgumentException("null證書");
final byte[] byteArray = switch (publicKey) {
case RSAKey rsaKey -> ... // RSA密鑰轉(zhuǎn)換為字節(jié)數(shù)組
case DSAPublicKey dsaKey -> ... // DSA公鑰轉(zhuǎn)換為字節(jié)數(shù)組
...
default -> ... // 其他情況處理邏輯
};
super(byteArray);
}
- 不需要對Java虛擬機(jī)進(jìn)行任何更改。這種 Java 語言特性僅依賴于 JVM 當(dāng)前驗證和執(zhí)行構(gòu)造函數(shù)中顯式構(gòu)造函數(shù)調(diào)用之前出現(xiàn)的代碼的能力。
String Templates - JEP 459:
字符串模板。字符串模板通過將文本文字與嵌入表達(dá)式和模板處理器相結(jié)合,以產(chǎn)生專門的結(jié)果,來補(bǔ)充 Java 的現(xiàn)有字符串文字和文本塊。
優(yōu)化:
- 通過簡化在運行時計算值的字符串的表達(dá)方式,簡化了編寫 Java 程序。
- 通過使文本和表達(dá)式混合的表達(dá)更易于閱讀,無論文本是否適合單個源行(如字符串文字)或跨越多個源行(如文本塊)。
- 通過支持模板及其嵌入表達(dá)式的驗證和轉(zhuǎn)換,改進(jìn)了由用戶提供的值組成字符串并將其傳遞給其他系統(tǒng)(例如,構(gòu)建數(shù)據(jù)庫查詢)的 Java 程序的安全性。
- **保持了靈活性,允許Java庫定義在字符串模板中使用的格式化語法。
- 簡化了接受非Java語言(例如`SQL`、`XML` 和 `JSON`)編寫的字符串的 API 的使用。
- 允許創(chuàng)建從文本文字和嵌入表達(dá)式計算出的非字符串值,而無需通過中間字符串表示轉(zhuǎn)換。
字符串的模板可以直接在代碼中表達(dá),就像注釋字符串一樣,Java 運行時會自動將特定于模板的規(guī)則應(yīng)用于字符串。從模板編寫字符串將使開發(fā)人員不必費力地轉(zhuǎn)義每個嵌入表達(dá)式、調(diào)用validate()
整個字符串或使用java.util.ResourceBundle
來查找本地化字符串。
比如我們可以構(gòu)造一個表示JSON文檔的字符串,然后將其提供給JSON解析器:
String name = "Joan Smith";
String phone = "555-123-4567";
String address = "1 Maple Drive, Anytown";
String json = """
{
"name": "%s",
"phone": "%s",
"address": "%s"
}
""".formatted(name, phone, address);
JSONObject doc = JSON.parse(json);
字符串的 JSON 結(jié)構(gòu)可以直接在代碼中表達(dá),Java運行時會JSONObject
自動將字符串轉(zhuǎn)換為。無需通過解析器進(jìn)行手動繞行。
我們使用基于模板的字符串組合機(jī)制,我們就可以提高幾乎每個Jav 程序的可讀性和可靠性。這種功能將提供插值的好處,就像在其他編程語言中看到的那樣,但不太容易引入安全漏洞。它還可以減少使用將復(fù)雜輸入作為字符串的庫的繁瑣。
我們還可以使用模板STR
處理器,STR
是 Java 平臺中定義的模板處理器。它通過將模板中的每個嵌入表達(dá)式替換為該表達(dá)式的(字符串化)值來執(zhí)行字符串插值。STR
是public
static
final
自動導(dǎo)入到每個Java源文件中的字段。
// Embedded expressions can be strings
String firstName = "Bill";
String lastName = "Duck";
String fullName = STR."\{firstName} \{lastName}";
| "Bill Duck"
String sortName = STR."\{lastName}, \{firstName}";
| "Duck, Bill"
// Embedded expressions can perform arithmetic
int x = 10, y = 20;
String s = STR."\{x} + \{y} = \{x + y}";
| "10 + 20 = 30"
// Embedded expressions can invoke methods and access fields
String s = STR."You have a \{getOfferType()} waiting for you!";
| "You have a gift waiting for you!"
String t = STR."Access at \{req.date} \{req.time} from \{req.ipAddress}";
| "Access at 2022-03-25 15:34 from 8.8.8.8"
模板表達(dá)式的模板可以跨越多行源代碼,使用類似于文本塊的語法。
String title = "My Web Page";
String text = "Hello, world";
String html = STR."""
<html>
<head>
<title>\{title}</title>
</head>
<body>
<p>\{text}</p>
</body>
</html>
""";
| """
| <html>
| <head>
| <title>My Web Page</title>
| </head>
| <body>
| <p>Hello, world</p>
| </body>
| </html>
| """
String name = "Joan Smith";
String phone = "555-123-4567";
String address = "1 Maple Drive, Anytown";
String json = STR."""
{
"name": "\{name}",
"phone": "\{phone}",
"address": "\{address}"
}
""";
| """
| {
| "name": "Joan Smith",
| "phone": "555-123-4567",
| "address": "1 Maple Drive, Anytown"
| }
| """
Implicitly Declared Classes and Instance Main Methods - JEP 463:
隱式聲明的類和實例主方法。這項Java增強(qiáng)引入了隱式聲明的類以及實例主方法的功能,允許開發(fā)人員在不明確顯式聲明類的情況下編寫類結(jié)構(gòu),并能夠在類實例上直接定義和執(zhí)行類似于傳統(tǒng)main
方法的入口點。這一特性旨在簡化編程模型,特別是對于初學(xué)者和小型腳本場景,使得無需了解大型程序設(shè)計所需的完整類聲明結(jié)構(gòu)也能快速編寫可運行的Java代碼。
優(yōu)化:
總體來說可以快速學(xué)習(xí)Java。
- 提供了平穩(wěn)的入門 Java 編程的途徑,因此教Java的可以逐漸介紹概念。
- 幫助初學(xué)者以簡潔的方式編寫基本程序,并隨著他們的技能增長而逐漸增加他們的代碼。
- 減少了編寫簡單程序(如腳本和命令行實用程序)的儀式感。
- 不會引入單獨的 Java 語言初學(xué)者方言。
- 不會引入單獨的初學(xué)者工具鏈;初學(xué)者的生程序應(yīng)該使用編譯和運行任何Java程序的相同工具。
我們以入門Java的第一行代碼Hello World
為例:
class HelloWorld {
void main() {
System.out.println("Hello, World!");
}
}
Java22還可以隱式聲明一個類:
void main() {
System.out.println("Hello, World!");
}
還可以這樣:
String greeting() { return "Hello, World!"; }
void main() {
System.out.println(greeting());
}
或者,使用字段,如:
String greeting = "Hello, World!";
void main() {
System.out.println(greeting);
}
Java API庫上的改進(jìn)
Foreign Function & Memory API - JEP 454:
外部函數(shù)和內(nèi)存API。允許Java程序與Java運行時之外的代碼和數(shù)據(jù)進(jìn)行交互。通過高效地調(diào)用外部函數(shù)(即JVM外部的代碼)和安全地訪問外部內(nèi)存(即JVM不管理的內(nèi)存),該API使Java程序能夠調(diào)用本地庫并處理本地數(shù)據(jù),而無需JNI的脆弱性和危險性。
優(yōu)化
- 生產(chǎn)率 —— 用簡潔、可讀和純 Java 的 API 替換原生方法和 Java 本機(jī)接口(JNI)的脆弱機(jī)制。
- 性能 —— 提供與 JNI 和 sun.misc.Unsafe 相當(dāng)甚至更好的外部函數(shù)和內(nèi)存訪問開銷。
- 廣泛的平臺支持 —— 在 JVM 運行的每個平臺上啟用本地庫的發(fā)現(xiàn)和調(diào)用。
- 統(tǒng)一性 —— 提供對結(jié)構(gòu)化和非結(jié)構(gòu)化數(shù)據(jù)的操作方式,無限大小,多種內(nèi)存類型(例如,本機(jī)內(nèi)存、持久內(nèi)存和托管堆內(nèi)存)。
- 健全性 —— 即使在多個線程之間分配和釋放內(nèi)存時,也保證不會出現(xiàn)使用后釋放的錯誤。
- 完整性 —— 允許程序執(zhí)行與本地代碼和數(shù)據(jù)有關(guān)的不安全操作,但默認(rèn)情況下向用戶警告此類操作。
Java22提供外部函數(shù)和內(nèi)存API(FFM API)定義類和接口,以便開發(fā)者使用他們可以
- 控制外部內(nèi)存
(MemorySegment
、Arena
和SegmentAllocator
)的分配和釋放, - 操作和訪問結(jié)構(gòu)化的外部存儲器
MemoryLayout
和VarHandle
- 調(diào)用外部函數(shù)
(Linker
、SymbolLookup
、FunctionDescriptor
和MethodHandle
)。
FFM API在java.lang.foreign
包中。
Class-File API - JEP 457:
類文件API。提供了用于解析、生成和轉(zhuǎn)換 Java 類文件的標(biāo)準(zhǔn) API。
優(yōu)化:
- 提供用于處理類文件的API,該類文件跟蹤Java虛擬機(jī)規(guī)范定義的文件格式。class
- 使JDK組件能夠遷移到標(biāo)準(zhǔn) API,并最終遷移到標(biāo)準(zhǔn)API刪除第三方ASM庫的JDK內(nèi)部副本。
Java22為 Class-File API 采用了以下設(shè)計目標(biāo)和原則。
- 類文件實體由不可變對象表示
所有類文件 實體,例如字段、方法、屬性、字節(jié)碼指令、注釋等, 由不可變對象表示。這有利于在以下情況下進(jìn)行可靠共享 正在轉(zhuǎn)換類文件。 - 樹結(jié)構(gòu)表示
類文件具有樹結(jié)構(gòu)。一個類 有一些元數(shù)據(jù)(名稱、超類等)和可變數(shù)量的字段, 方法和屬性。字段和方法本身具有元數(shù)據(jù),并進(jìn)一步 包含屬性,包括屬性。屬性 further 包含指令、異常處理程序等。用于 導(dǎo)航和生成類文件應(yīng)反映此結(jié)構(gòu)。CodeCode - 用戶驅(qū)動的導(dǎo)航
我們通過類文件樹的路徑是 由用戶選擇驅(qū)動。如果用戶只關(guān)心字段上的注釋,那么 我們只需要解析結(jié)構(gòu)內(nèi)部的注釋屬性;我們不應(yīng)該研究任何一個類 屬性或方法的主體,或字段的其他屬性。用戶 應(yīng)該能夠處理復(fù)合實體,例如方法,無論是作為單個實體 單位或作為其組成部分的流,根據(jù)需要。field_info - 懶惰
用戶驅(qū)動的導(dǎo)航可顯著提高效率,例如 不解析超過滿足用戶要求的類文件 需要。如果用戶不打算深入研究方法的內(nèi)容,那么我們 不需要解析比需要更多的結(jié)構(gòu) 下一個類文件元素開始的位置。我們可以懶洋洋地膨脹和緩存, 用戶請求時的完整表示形式。method_info - 統(tǒng)一的流式處理和具體化視圖
與 ASM 一樣,我們希望同時支持兩者 類文件的流式處理和實例化視圖。流視圖是 適用于大多數(shù)用例,而物化視圖更 一般,因為它支持隨機(jī)訪問。我們可以提供一個物化的觀點 通過懶惰比 ASM 便宜,這是由不變性實現(xiàn)的。我們可以, 此外,對齊流式視圖和實例化視圖,以便它們使用通用的 詞匯表,可以協(xié)調(diào)使用,因為每個用例都很方便。 - 緊急轉(zhuǎn)換
如果類文件解析和生成 API 是 充分對齊,那么轉(zhuǎn)換可以是一個緊急屬性,可以 不需要自己的特殊模式或重要的新 API 圖面。(ASM實現(xiàn) 這是通過為讀者和作者使用通用的訪客結(jié)構(gòu)來實現(xiàn)的。如果類, 字段、方法和代碼體是可讀和可寫的,作為 元素,則可以將轉(zhuǎn)換視為對此的平面映射操作 流,由 lambda 定義。 - 細(xì)節(jié)隱藏
類文件的許多部分(常量池、引導(dǎo)方法 表、堆棧圖等)派生自類文件的其他部分。它 要求用戶直接構(gòu)建這些是沒有意義的;這是額外的工作 對于用戶來說,并增加了出錯的機(jī)會。API 將自動 生成與其他實體緊密耦合的實體 添加到類文件中的字段、方法和指令。
Class-File API
在 java.lang.classfile
包和子包中。 它定義了三個主要抽象:
- 元素是對類文件某部分的一種不可變描述,可能是一個指令、屬性、字段、方法,甚至是整個類文件。有些元素,如方法,是復(fù)合元素;除了本身是元素外,它們還包含自身的元素,可以作為一個整體處理,也可以進(jìn)一步分解。
- 每種類型的復(fù)合元素都有一個對應(yīng)的構(gòu)建器,該構(gòu)建器擁有特定的構(gòu)建方法(例如,ClassBuilder::withMethod),并且也是相應(yīng)元素類型的消費者。
- 最后,變換代表了一個函數(shù),它接收一個元素和一個構(gòu)建器,并調(diào)解如何(如果有的話)將該元素轉(zhuǎn)換為其他元素。
Stream Gatherers - JEP 461:
流收集器。增強(qiáng)了 Stream API,以支持自定義中間操作。這將允許流管道以不易通過現(xiàn)有內(nèi)置中間操作實現(xiàn)的方式轉(zhuǎn)換數(shù)據(jù)。
優(yōu)化:
- 使流管道更加靈活和富有表現(xiàn)力。
- 盡可能允許自定義中間操作來操作無限大小的流。
流(Stream)::gather(Gatherer) 是一種新的中間流操作,通過應(yīng)用用戶自定義實體——收集器(Gatherer)來處理流中的元素。利用gather操作,我們可以構(gòu)建高效且適用于并行處理的流,實現(xiàn)幾乎所有的中間操作。Stream::gather(Gatherer) 在中間操作中的作用類似于Stream::collect(Collector)在終止操作中的作用。
Gatherer用于表示對流中元素的轉(zhuǎn)換,它是java.util.stream.Gatherer接口的一個實例。Gatherer可以以一對一、一對多、多對一或多對多的方式轉(zhuǎn)換元素。它可以跟蹤已處理過的元素以影響后續(xù)元素的轉(zhuǎn)換,支持短路操作以將無限流轉(zhuǎn)換為有限流,并能啟用并行執(zhí)行。例如,一個Gatherer可以從輸入流中按條件轉(zhuǎn)換一個輸入元素為一個輸出元素,直到某一條件變?yōu)檎?,此時開始將一個輸入元素轉(zhuǎn)換為兩個輸出元素。
Gatherer由四個協(xié)同工作的函數(shù)定義:
- 可選初始化函數(shù)提供了在處理流元素過程中維持私有狀態(tài)的對象。例如,Gatherer可以存儲當(dāng)前元素,以便下次應(yīng)用時比較新元素和前一個元素,并僅輸出兩者中較大的那個。實際上,這種Gatherer將兩個輸入元素轉(zhuǎn)換為一個輸出元素。
- 整合函數(shù)整合來自輸入流的新元素,可能檢查私有狀態(tài)對象,并可能向輸出流發(fā)出元素。它還可以在到達(dá)輸入流末尾之前提前終止處理;例如,一個搜索整數(shù)流中最大值的Gatherer在檢測到Integer.MAX_VALUE時可以終止處理。
- 可選組合函數(shù)可用于在輸入流標(biāo)記為并行時并行評估Gatherer。若Gatherer不支持并行,則仍可作為并行流管道的一部分,但在評估時將以順序方式進(jìn)行。這對于某些本質(zhì)上有序因而無法并行化的操作場景非常有用。
- 可選完成函數(shù)在沒有更多輸入元素要消費時被調(diào)用。該函數(shù)可以檢查私有狀態(tài)對象,并可能發(fā)出額外的輸出元素。例如,在輸入元素中搜索特定元素的Gatherer在其完成器被調(diào)用時,若未能找到目標(biāo)元素,可以通過拋出異常等方式報告失敗。
當(dāng)調(diào)用Stream::gather時,執(zhí)行以下等效步驟:
- 創(chuàng)建一個Downstream對象,當(dāng)接收到Gatherer輸出類型的元素時,將其傳遞到管道中的下一階段。
- 通過調(diào)用其initializer的get()方法獲取Gatherer的私有狀態(tài)對象。
- 通過調(diào)用其integrator()方法獲取Gatherer的整合器。
- 當(dāng)存在更多輸入元素時,調(diào)用整合器的integrate(…)方法,傳入狀態(tài)對象、下一個元素和下游對象。若該方法返回false,則終止處理。
- 獲取Gatherer的完成器并使用狀態(tài)對象和下游對象對其調(diào)用。
現(xiàn)有Stream接口中聲明的所有中間操作都可以通過調(diào)用帶有實現(xiàn)該操作的Gatherer的gather方法來實現(xiàn)。例如,對于一個T類型元素的流,Stream::map通過應(yīng)用一個函數(shù)將每個T元素轉(zhuǎn)換為U元素并將其向下傳遞;這實質(zhì)上就是一個無狀態(tài)的一對一Gatherer。另一個例子是Stream::filter,它采用一個謂詞決定輸入元素是否應(yīng)向下傳遞;這只是一個無狀態(tài)的一對多Gatherer。事實上,從概念上講,每一個流管道都可以等同于:
source.gather(...).gather(...).gather(...).collect(...)
Structured Concurrency - JEP 462:
結(jié)構(gòu)化并發(fā)。簡化了并發(fā)編程。結(jié)構(gòu)化并發(fā)將在不同線程中運行的相關(guān)任務(wù)組視為單個工作單元,從而簡化了錯誤處理和取消,提高了可靠性并增強(qiáng)了可觀察性。
優(yōu)化:
- 促進(jìn)一種并發(fā)編程風(fēng)格,能夠消除由于取消和關(guān)閉產(chǎn)生的常見風(fēng)險,例如線程泄露和取消延遲。
- 提升并發(fā)代碼的可觀測性。
結(jié)構(gòu)化并發(fā)API的主要類是java.util.concurrent
包中的StructuredTaskScope
類。此類允許開發(fā)人員將任務(wù)結(jié)構(gòu)化為一組并發(fā)子任務(wù),并將它們作為一個整體進(jìn)行協(xié)調(diào)管理。子任務(wù)通過分別創(chuàng)建新線程(fork)并在之后作為一個整體等待它們完成(join)和可能的情況下作為一個整體取消它們。子任務(wù)的成功結(jié)果或異常將被父任務(wù)聚合并處理。StructuredTaskScope
確保了子任務(wù)的生命周期被限定在一個清晰的詞法作用域內(nèi),在這個作用域內(nèi),任務(wù)與其子任務(wù)的所有交互,包括分叉(fork
)、同步(join
)、取消(cancellation
)、錯誤處理和結(jié)果合成都在此發(fā)生。
使用StructuredTaskScopes實例:
Response handle() throws ExecutionException, InterruptedException {
try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {
Supplier<String> user = scope.fork(() -> findUser());
Supplier<Integer> order = scope.fork(() -> fetchOrder());
scope.join() // 同步兩個子任務(wù)
.throwIfFailed(); // 并傳播錯誤信息
// 這里,兩個子任務(wù)都已經(jīng)成功,所以組合它們的結(jié)果
return new Response(user.get(), order.get());
}
}
這里理解涉及線程的生命周期變得簡單:在所有情況下,它們的生命周期都被限制在一個詞法作用域內(nèi),即try-with-resources
語句的主體內(nèi)。此外,使用StructuredTaskScope
確保了一系列有價值的特性:
- 錯誤處理與短路機(jī)制——如果
findUser()
或fetchOrder()
子任務(wù)之一失敗,尚未完成的另一個子任務(wù)將被取消。(這是由ShutdownOnFailure
實現(xiàn)的關(guān)閉策略管理的,也有可能實現(xiàn)其他策略)。 - 取消傳播——如果在調(diào)用
join()
之前或期間執(zhí)行handle()
方法的線程被中斷,則當(dāng)線程退出作用域時,兩個子任務(wù)都將自動取消。 - 清晰性——上述代碼具有清晰的結(jié)構(gòu):設(shè)置子任務(wù),等待它們完成或被取消,然后決定是否成功(并處理已完成子任務(wù)的結(jié)果)或失?。ㄗ尤蝿?wù)已經(jīng)結(jié)束,因此無需進(jìn)一步清理)。
- 可觀測性——如下面所述,線程轉(zhuǎn)儲能夠清晰地顯示任務(wù)層級關(guān)系,執(zhí)行
findUser()
和fetchOrder()
的線程在轉(zhuǎn)儲中顯示為scope的子線程。
Scoped Values - JEP 464:
作用域值優(yōu)化。在線程內(nèi)和跨線程之間有效共享不可變數(shù)據(jù)。這個Java增強(qiáng)它旨在提供一種機(jī)制,允許開發(fā)者在Java應(yīng)用程序中安全地在線程內(nèi)部以及跨線程之間共享不可變數(shù)據(jù)。該特性旨在替代或改善現(xiàn)有的ThreadLocal機(jī)制,提供一種更加可控、易于管理和高效的解決方案,尤其是在涉及大規(guī)模并發(fā)處理和跨層數(shù)據(jù)傳遞場景時。通過范圍值,開發(fā)人員可以更好地組織和管理在特定作用域內(nèi)有效的變量,同時確保資源的有效利用和數(shù)據(jù)的安全共享。
優(yōu)化:
- 易用性 — 理解數(shù)據(jù)流應(yīng)當(dāng)輕松直觀。
- 可理解性 — 共享數(shù)據(jù)的生命周期可以從代碼的語法結(jié)構(gòu)中清晰可見。
- 健壯性 — 調(diào)用者共享的數(shù)據(jù)只能被合法的被調(diào)用者獲取。
- 性能 — 數(shù)據(jù)能夠有效地在大量線程間高效共享。
作用域值是一種容器對象,允許方法在同一個線程內(nèi)安全高效地將其數(shù)據(jù)值與直接和間接的被調(diào)用者共享,同時也能與子線程共享,而無需依賴方法參數(shù)。它是一個類型為ScopedValue
的變量,通常被聲明為final static
字段,并設(shè)置為private
訪問權(quán)限,以防止其他類的代碼直接訪問。
類似線程局部變量,作用域值關(guān)聯(lián)了多個值,每個線程對應(yīng)一個。具體使用的值取決于哪個線程調(diào)用了它的方法。不同于線程局部變量,范圍限定值只被寫入一次,并且在線程執(zhí)行期間只能在一定時間段內(nèi)可用。
作用域值的使用如下所示。一些代碼調(diào)用ScopedValue.where
,提供一個范圍限定值及其要綁定的對象。調(diào)用run方法會綁定該范圍限定值,為當(dāng)前線程提供一個特定的副本,然后執(zhí)行作為參數(shù)傳遞的lambda
表達(dá)式。在run方法執(zhí)行期間,lambda
表達(dá)式或從中直接或間接調(diào)用的任何方法,都可以通過值的get()
方法讀取范圍限定值。當(dāng)run方法執(zhí)行完畢后,該綁定關(guān)系會被銷毀。
final static ScopedValue<...> NAME = ScopedValue.newInstance();
// 在某個方法中
ScopedValue.where(NAME, <value>)
.run(() -> { ... NAME.get() ... 調(diào)用方法 ... });
// 在lambda表達(dá)式中直接或間接調(diào)用的方法中
... NAME.get() ...
代碼的結(jié)構(gòu)明確了線程可以讀取其作用域值副本的時間段。這個有限的生命周期極大地簡化了對線程行為的推理。數(shù)據(jù)從調(diào)用者單向傳輸至直接和間接的被調(diào)用者,一眼就能看出。不存在能讓遠(yuǎn)端代碼隨時改變范圍限定值的set方法。這也有助于提高性能:無論調(diào)用者和被調(diào)用者的棧距離如何,通過get()方法讀取作用域值的速度常常與讀取局部變量一樣快。
Vector API - JEP 460:
矢量API。一個能夠在支持的CPU架構(gòu)上運行時可靠編譯為最優(yōu)矢量指令的API,從而實現(xiàn)優(yōu)于等效標(biāo)量計算的性能。
本JEP提議在JDK 22中重新孵化該API,相比于JDK 21版本,API進(jìn)行了些許增強(qiáng)。實現(xiàn)內(nèi)容包括bug修復(fù)和性能優(yōu)化。主要變更如下:
支持通過任意原始元素類型的數(shù)組支持的堆內(nèi)存MemorySegments
進(jìn)行矢量訪問。在此之前,訪問僅限于由字節(jié)數(shù)組支持的堆內(nèi)存MemorySegments
。
優(yōu)化:
- 清晰簡潔的API
API應(yīng)該能夠清晰簡潔地表述一系列由循環(huán)內(nèi)矢量操作序列組成的各種矢量計算,可能還包括控制流程。應(yīng)支持針對矢量大小或每矢量的通道數(shù)進(jìn)行泛型表達(dá),從而使這類計算能夠在支持不同矢量大小的硬件之間移植。 - 平臺無關(guān)性
API應(yīng)獨立于CPU架構(gòu),支持在多種支持矢量指令的架構(gòu)上實現(xiàn)。按照J(rèn)ava API的一貫原則,在平臺優(yōu)化和可移植性產(chǎn)生沖突時,我們會傾向于使API更具可移植性,即使這意味著某些特定于平臺的慣用法無法在便攜代碼中表達(dá)。 - 在x64和AArch64架構(gòu)上的可靠運行時編譯和高性能
在具備能力的x64架構(gòu)上,Java運行時環(huán)境,特別是HotSpot C2編譯器,應(yīng)將矢量操作編譯為相應(yīng)的高效矢量指令,比如Streaming SIMD Extensions (SSE) 和Advanced Vector Extensions (AVX)支持的那些指令。開發(fā)者應(yīng)有信心他們所表達(dá)的矢量操作將可靠地緊密映射到相關(guān)的矢量指令上。在具備能力的ARM AArch64架構(gòu)上,C2同樣會將矢量操作編譯為NEON和SVE支持的矢量指令。 - 優(yōu)雅降級
有時矢量計算可能無法完全在運行時表述為矢量指令序列,可能是因為架構(gòu)不支持所需的某些指令。在這種情況下,Vector API實現(xiàn)應(yīng)能夠優(yōu)雅降級并仍然正常運作。這可能包括在矢量計算無法高效編譯為矢量指令時發(fā)出警告。在不支持矢量的平臺上,優(yōu)雅降級將生成與手動展開循環(huán)相競爭的代碼,其中展開因子為所選矢量的通道數(shù)。 - 與
Project Valhalla
項目的契合Vector API
的長期目標(biāo)是利用Project Valhalla
對Java對象模型的增強(qiáng)功能。主要來說,這意味著將Vector API
當(dāng)前基于值的類更改為值類,以便程序能夠處理值對象,即缺乏對象標(biāo)識性的類實例。因此,Vector API
將在多個版本中孵化,直至Project Valhalla
的必要特性作為預(yù)覽功能可用。一旦這些Valhalla特性可用,我們將調(diào)整Vector API及其實現(xiàn)以使用這些特性,并將Vector API
本身提升為預(yù)覽功能。
向量由抽象類Vector<E>
表示。類型變量E被實例化為矢量覆蓋的標(biāo)量基本整數(shù)或浮點元素類型的裝箱類型。一個向量還具有形狀屬性,該屬性定義了矢量的大?。ㄒ晕粸閱挝唬.?dāng)矢量計算由HotSpot C2
編譯器編譯時,向量的形狀決定了Vector<E>
實例如何映射到硬件矢量寄存器。向量的長度,即車道數(shù)或元素個數(shù),等于矢量大小除以元素大小。
支持的一系列元素類型(E)包括Byte、Short、Integer、Long、Float和Double,分別對應(yīng)于標(biāo)量基本類型byte、short、int、long、float和double。
支持的一系列形狀對應(yīng)于64位、128位、256位和512位的矢量大小,以及最大位數(shù)。512位形狀可以將字節(jié)打包成64個車道,或者將整數(shù)打包成16個車道,具有這種形狀的矢量可以一次性操作64個字節(jié)或16個整數(shù)。max-bits形狀支持當(dāng)前架構(gòu)的最大矢量尺寸,這使得API能夠支持ARM SVE
平臺,該平臺實現(xiàn)可以支持從128位到2048位,以128位為增量的任何固定尺寸。
性能上的改進(jìn)
Regional Pinning for G1 - JEP 423:
區(qū)域固定。通過在G1中實現(xiàn)區(qū)域固定(regional pinning),從而在Java Native Interface (JNI) 臨界區(qū)域內(nèi)不需要禁用垃圾收集,以此來減少延遲。
優(yōu)化:
- 不會因JNI臨界區(qū)域?qū)е戮€程停滯。
- 不會因JNI臨界區(qū)域而導(dǎo)致垃圾收集啟動時增加額外延遲。
- 當(dāng)沒有JNI臨界區(qū)域活動時,GC暫停時間不會出現(xiàn)倒退。
- 當(dāng)JNI臨界區(qū)域活動時,GC暫停時間只會有最小程度的倒退。
工具類
Launch Multi-File Source-Code Programs - JEP 458:
啟動多文件源代碼程序。允許用戶在不首先編譯程序的情況下運行由多個 Java 源代碼文件提供的程序。
優(yōu)化:
- 通過使從小型程序向大型程序的過渡更加漸進(jìn),使開發(fā)人員能夠選擇何時以及何時費力地配置構(gòu)建工具,提高了開發(fā)人員的生產(chǎn)力。
Java22增強(qiáng)了Java啟動器的源文件模式,使其能夠運行以多份Java源代碼文件形式提供的程序。
舉例來說,假設(shè)一個目錄包含了兩個文件:Prog.java和Helper.java,每個文件各聲明一個類:
// Prog.java
class Prog {
public static void main(String[] args) { Helper.run(); }
}
// Helper.java
class Helper {
static void run() { System.out.println("Hello!"); }
}
運行命令java Prog.java
將會在內(nèi)存中編譯Prog類并調(diào)用其main方法。由于Prog類中的代碼引用了Helper類,啟動器會在文件系統(tǒng)中查找Helper.java文件,并在內(nèi)存中編譯Helper類。如果Helper類中的代碼又引用了其他類,例如HelperAux類,那么啟動器還會找到HelperAux.java并對其進(jìn)行編譯。
當(dāng)不同.java文件中的類互相引用時,Java啟動器并不保證按照特定順序或時間點編譯這些.java文件。例如,啟動器可能先編譯Helper.java再編譯Prog.java。有些代碼可能在程序開始執(zhí)行前就已經(jīng)被編譯,而其他代碼可能在需要時才懶加載編譯。
只有被程序引用到的類所在的.java文件才會被編譯。這樣一來,開發(fā)者可以在嘗試新版本代碼時不必?fù)?dān)心舊版本會被意外編譯。例如,假設(shè)目錄中還包含OldProg.java文件,其中包含Progr類的一個舊版本,該版本期望Helper類有一個名為go的方法而不是run方法。當(dāng)運行Prog.java時,存在包含潛在錯誤的OldProg.java文件并不會影響程序執(zhí)行。
一個.java文件中可以聲明多個類,且會被一起編譯。在一個.java文件中共聲明的類優(yōu)先于在其他.java文件中聲明的類。例如,假設(shè)上面的Prog.java文件擴(kuò)展后也在其中聲明了Helper類,盡管Helper.java文件中已有一個同名類。當(dāng)Prog.java中的代碼引用Helper時,會使用在Prog.java中共同聲明的那個類;啟動器不會去搜索Helper.java文件。
源代碼程序中禁止重復(fù)的類聲明。也就是說,同一個.java文件內(nèi)或構(gòu)成程序的不同.java文件之間的類聲明,如果名稱相同,則不允許存在。假設(shè)經(jīng)過編輯后,Prog.java和Helper.java最終變成以下形式,其中類Aux意外地在兩個文件中都被聲明:
// Prog.java
class Prog {
public static void main(String[] args) { Helper.run(); Aux.cleanup(); }
}
class Aux {
static void cleanup() { ... }
}
// Helper.java
class Helper {
static void run() { ... }
}
class Aux {
static void cleanup() { ... }
}
運行命令java Prog.java
會編譯Prog.java中的Prog和Aux類,調(diào)用Prog類的main方法,然后——由于main方法引用了Helper——查找并編譯Helper.java中的Helper和Aux類。Helper.java中對Aux類的重復(fù)聲明是不允許的,所以程序會停止運行,啟動器報告錯誤。
當(dāng)通過Java啟動器傳遞單個.java文件名稱時,就會觸發(fā)其源文件模式。如果提供了額外的文件名,它們會成為主方法的參數(shù)。例如,運行命令java Prog.java Helper.java
會導(dǎo)致字符串?dāng)?shù)組"Helper.java"作為參數(shù)傳給Prog類的main方法。
其他特性
除了JEP中描述的更改之外,發(fā)行說明中還列出了許多較小的更新,這些更新對許多應(yīng)用程序開發(fā)者有重要意義。其中包括廢棄過時的API和移除先前已經(jīng)棄用的API。
- 向keytool和jarsigner添加了更多算法。
- 垃圾回收器吞吐量方面的改進(jìn),特別是在“年輕代”垃圾回收方面。
- 改進(jìn)了系統(tǒng)模塊描述符的版本報告功能。
- 提高了對原生代碼“等待”處理選項的完善。
- Unicode通用區(qū)域數(shù)據(jù)倉庫已更新至版本44。
- 支持從字節(jié)碼加載的類型上的類型注解。
- ForkJoinPool和ForkJoinTask現(xiàn)在能更好地處理不可中斷任務(wù)。
- 對客戶端與服務(wù)器TLS連接屬性配置提供了更多的靈活性。
- 提高了對原生內(nèi)存跟蹤的功能,包括峰值使用情況的報告。
- 最后,如同所有特性發(fā)布版一樣,JDK 22包含了數(shù)百項性能、穩(wěn)定性和安全性更新,包括適應(yīng)底層操作系統(tǒng)和固件更新及標(biāo)準(zhǔn)變化。用戶和應(yīng)用程序開發(fā)者通常在不知不覺中受益于這些變化。
最后,JDK 22是通過六個月的發(fā)布節(jié)奏按時交付的13th功能版本。由于預(yù)期改進(jìn)源源不斷,這種程度的可預(yù)測性使開發(fā)人員能夠輕松管理創(chuàng)新的采用。Oracle 不會為 JDK 22 提供長期支持,在 2023 年 9 月之前提供更新,之后它將被 Oracle JDK 23 取代。最近的長期維護(hù)版本是Java 21。