Java22重磅釋出!!!!卷不動了,真的卷不動了。。。。

码农Academy發表於2024-03-21

就在3月19日,Java22重磅釋出。Java22新增了12項增強功能,其中包括七個預覽特性和一個孵化器特性,這些功能都顯著到足以引起JDK增強提案(JEPs)的關注。它們涵蓋了Java語言、其API、效能以及JDK中包含的工具的改進。

真的卷不動了,,前段時間才將專案升級到Java17。。。。

接下來我們看看具體的新特性介紹。。。

image.png

Java語言上的改進

Unnamed Variables & Patterns - JEP 456

匿名變數和模式。當需要但未使用變數宣告或巢狀模式時,提高了可讀性。這兩者都用下劃線字元表示。

最佳化:

  1. 捕獲開發人員意圖,即給定的繫結或Lambda引數未使用,並強制執行該屬性以澄清程式並減少錯誤的機會。

比如我們可以在迴圈中這樣使用:

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迴圈:

while (q.size() >= 3) {
    var x = q.remove();
    var _ = q.remove();       // Unnamed variable
    var _ = q.remove();       // Unnamed variable
    ... new Point(x, 0) ...
}
  1. 透過識別必須宣告但未使用的變數(例如,在捕獲子句中)來提高所有程式碼的可維護性。
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
  1. 允許在單個 case 標籤中出現多個模式,如果它們都沒有宣告任何模式變數。

例如:

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
}

透過這種改進允許我們省略名稱,未命名的模式變數使得基於型別模式的執行時資料探索在switch語句塊以及使用instanceof運算子時,視覺上更加清晰明瞭。

  1. 透過省略不必要的巢狀型別模式來改善記錄模式的可讀性。

Statements before super - JEP 447

構造器中的前置語句。在建構函式中,允許在顯式建構函式呼叫之前出現不引用正在建立的例項的語句。

最佳化:

  1. 為開發人員提供更大的自由度來表達建構函式的行為,從而使當前必須因子化為輔助靜態方法、輔助中間建構函式或建構函式引數的邏輯能夠更自然地放置。

有時我們需要驗證傳遞給超類建構函式的引數。雖然我們可以在事後進行引數驗證,但這意味著可能會進行不必要的操作。例如如下:

public class PositiveBigInteger extends BigInteger {

    public PositiveBigInteger(long value) {
        super(value);               // Potentially unnecessary work
        if (value <= 0)
            throw new IllegalArgumentException("non-positive value");
    }
}

Java22中的做法是宣告一個能夠快速失敗的建構函式,即在呼叫超類建構函式之前先驗證其引數。目前我們只能採用內聯方式實現這一點,即藉助於輔助靜態方法:

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;
    }

}

我們還可以將驗證邏輯直接包含在建構函式內部,這段程式碼將會更具可讀性。

public class PositiveBigInteger extends BigInteger {

    public PositiveBigInteger(long value) {
        if (value <= 0)
            throw new IllegalArgumentException("non-positive value");
        super(value);
    }

}
  1. 保留了建構函式在類例項化期間按自上而下順序執行的現有保證,確保子類建構函式中的程式碼不能干擾超類例項化。

為了給超類建構函式提供引數,我們必須執行另外的計算,再次不得不借助於輔助方法:

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 -> ...
        };
    }

}

超類建構函式接受一個位元組陣列作為引數,而子類建構函式接受一個Certificate物件作為引數。為了滿足超類建構函式呼叫必須為子類建構函式中的第一條語句這一限制,我們宣告瞭一個輔助方法prepareByteArray來為此呼叫準備引數。

如果能夠將引數準備程式碼直接嵌入到建構函式中,這段程式碼會更具可讀性。在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金鑰轉換為位元組陣列
            case DSAPublicKey dsaKey -> ... // DSA公鑰轉換為位元組陣列
            ...
            default -> ... // 其他情況處理邏輯
        };
        super(byteArray);
    }
  1. 不需要對Java虛擬機器進行任何更改。這種 Java 語言特性僅依賴於 JVM 當前驗證和執行建構函式中顯式建構函式呼叫之前出現的程式碼的能力。

String Templates - JEP 459:

字串模板。字串模板透過將文字文字與嵌入表示式和模板處理器相結合,以產生專門的結果,來補充 Java 的現有字串文字和文字塊。

最佳化:

  1. 透過簡化在執行時計算值的字串的表達方式,簡化了編寫 Java 程式。
  2. 透過使文字和表示式混合的表達更易於閱讀,無論文字是否適合單個源行(如字串文字)或跨越多個源行(如文字塊)。
  3. 透過支援模板及其嵌入表示式的驗證和轉換,改進了由使用者提供的值組成字串並將其傳遞給其他系統(例如,構建資料庫查詢)的 Java 程式的安全性。
  4. **保持了靈活性,允許Java庫定義在字串模板中使用的格式化語法。
  5. 簡化了接受非Java語言(例如SQLXMLJSON)編寫的字串的 API 的使用。
  6. 允許建立從文字文字和嵌入表示式計算出的非字串值,而無需透過中間字串表示轉換。

字串的模板可以直接在程式碼中表達,就像註釋字串一樣,Java 執行時會自動將特定於模板的規則應用於字串。從模板編寫字串將使開發人員不必費力地轉義每個嵌入表示式、呼叫validate()整個字串或使用java.util.ResourceBundle來查詢本地化字串。

比如我們可以構造一個表示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 結構可以直接在程式碼中表達,Java執行時會JSONObject自動將字串轉換為。無需透過解析器進行手動繞行。
我們使用基於模板的字串組合機制,我們就可以提高几乎每個Jav 程式的可讀性和可靠性。這種功能將提供插值的好處,就像在其他程式語言中看到的那樣,但不太容易引入安全漏洞。它還可以減少使用將複雜輸入作為字串的庫的繁瑣。

我們還可以使用模板STR處理器,STR是 Java 平臺中定義的模板處理器。它透過將模板中的每個嵌入表示式替換為該表示式的(字串化)值來執行字串插值。STRpublic static final自動匯入到每個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"

模板表示式的模板可以跨越多行原始碼,使用類似於文字塊的語法。

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增強引入了隱式宣告的類以及例項主方法的功能,允許開發人員在不明確顯式宣告類的情況下編寫類結構,並能夠在類例項上直接定義和執行類似於傳統main方法的入口點。這一特性旨在簡化程式設計模型,特別是對於初學者和小型指令碼場景,使得無需瞭解大型程式設計所需的完整類宣告結構也能快速編寫可執行的Java程式碼。

最佳化:
總體來說可以快速學習Java。

  1. 提供了平穩的入門 Java 程式設計的途徑,因此教Java的可以逐漸介紹概念。
  2. 幫助初學者以簡潔的方式編寫基本程式,並隨著他們的技能增長而逐漸增加他們的程式碼。
  3. 減少了編寫簡單程式(如指令碼和命令列實用程式)的儀式感。
  4. 不會引入單獨的 Java 語言初學者方言。
  5. 不會引入單獨的初學者工具鏈;初學者的生程式應該使用編譯和執行任何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庫上的改進

Foreign Function & Memory API - JEP 454:

外部函式和記憶體API。允許Java程式與Java執行時之外的程式碼和資料進行互動。透過高效地呼叫外部函式(即JVM外部的程式碼)和安全地訪問外部記憶體(即JVM不管理的記憶體),該API使Java程式能夠呼叫本地庫並處理本地資料,而無需JNI的脆弱性和危險性。

最佳化

  1. 生產率 —— 用簡潔、可讀和純 Java 的 API 替換原生方法和 Java 本機介面(JNI)的脆弱機制。
  2. 效能 —— 提供與 JNI 和 sun.misc.Unsafe 相當甚至更好的外部函式和記憶體訪問開銷。
  3. 廣泛的平臺支援 —— 在 JVM 執行的每個平臺上啟用本地庫的發現和呼叫。
  4. 統一性 —— 提供對結構化和非結構化資料的操作方式,無限大小,多種記憶體型別(例如,本機記憶體、持久記憶體和託管堆記憶體)。
  5. 健全性 —— 即使在多個執行緒之間分配和釋放記憶體時,也保證不會出現使用後釋放的錯誤。
  6. 完整性 —— 允許程式執行與原生代碼和資料有關的不安全操作,但預設情況下向使用者警告此類操作。

Java22提供外部函式和記憶體API(FFM API)定義類和介面,以便開發者使用他們可以

  • 控制外部記憶體
    MemorySegmentArenaSegmentAllocator)的分配和釋放,
  • 操作和訪問結構化的外部儲存器
    MemoryLayoutVarHandle
  • 呼叫外部函式
    LinkerSymbolLookupFunctionDescriptorMethodHandle)。

FFM API在java.lang.foreign包中。

Class-File API - JEP 457:

類檔案API。提供了用於解析、生成和轉換 Java 類檔案的標準 API。

最佳化:

  1. 提供用於處理類檔案的API,該類檔案跟蹤Java虛擬機器規範定義的檔案格式。class
  2. 使JDK元件能夠遷移到標準 API,並最終遷移到標準API刪除第三方ASM庫的JDK內部副本。

Java22為 Class-File API 採用了以下設計目標和原則。

  • 類檔案實體由不可變物件表示
    所有類檔案 實體,例如欄位、方法、屬性、位元組碼指令、註釋等, 由不可變物件表示。這有利於在以下情況下進行可靠共享 正在轉換類檔案。

  • 樹結構表示
    類檔案具有樹結構。一個類 有一些後設資料(名稱、超類等)和可變數量的欄位, 方法和屬性。欄位和方法本身具有後設資料,並進一步 包含屬性,包括屬性。屬性 further 包含指令、異常處理程式等。用於 導航和生成類檔案應反映此結構。CodeCode

  • 使用者驅動的導航
    我們透過類檔案樹的路徑是 由使用者選擇驅動。如果使用者只關心欄位上的註釋,那麼 我們只需要解析結構內部的註釋屬性;我們不應該研究任何一個類 屬性或方法的主體,或欄位的其他屬性。使用者 應該能夠處理複合實體,例如方法,無論是作為單個實體 單位或作為其組成部分的流,根據需要。field_info

  • 懶惰
    使用者驅動的導航可顯著提高效率,例如 不解析超過滿足使用者要求的類檔案 需要。如果使用者不打算深入研究方法的內容,那麼我們 不需要解析比需要更多的結構 下一個類檔案元素開始的位置。我們可以懶洋洋地膨脹和快取, 使用者請求時的完整表示形式。method_info

  • 統一的流式處理和具體化檢視
    與 ASM 一樣,我們希望同時支援兩者 類檔案的流式處理和例項化檢視。流檢視是 適用於大多數用例,而物化檢視更 一般,因為它支援隨機訪問。我們可以提供一個物化的觀點 透過懶惰比 ASM 便宜,這是由不變性實現的。我們可以, 此外,對齊流式檢視和例項化檢視,以便它們使用通用的 詞彙表,可以協調使用,因為每個用例都很方便。

  • 緊急轉換
    如果類檔案解析和生成 API 是 充分對齊,那麼轉換可以是一個緊急屬性,可以 不需要自己的特殊模式或重要的新 API 圖面。(ASM實現 這是透過為讀者和作者使用通用的訪客結構來實現的。如果類, 欄位、方法和程式碼體是可讀和可寫的,作為 元素,則可以將轉換視為對此的平面對映操作 流,由 lambda 定義。

  • 細節隱藏
    類檔案的許多部分(常量池、引導方法 表、堆疊圖等)派生自類檔案的其他部分。它 要求使用者直接構建這些是沒有意義的;這是額外的工作 對於使用者來說,並增加了出錯的機會。API 將自動 生成與其他實體緊密耦合的實體 新增到類檔案中的欄位、方法和指令。

Class-File APIjava.lang.classfile 包和子包中。 它定義了三個主要抽象:

  • 元素是對類檔案某部分的一種不可變描述,可能是一個指令、屬性、欄位、方法,甚至是整個類檔案。有些元素,如方法,是複合元素;除了本身是元素外,它們還包含自身的元素,可以作為一個整體處理,也可以進一步分解。

  • 每種型別的複合元素都有一個對應的構建器,該構建器擁有特定的構建方法(例如,ClassBuilder::withMethod),並且也是相應元素型別的消費者。

  • 最後,變換代表了一個函式,它接收一個元素和一個構建器,並調解如何(如果有的話)將該元素轉換為其他元素。

Stream Gatherers - JEP 461:

流收集器。增強了 Stream API,以支援自定義中間操作。這將允許流管道以不易透過現有內建中間操作實現的方式轉換資料。

最佳化:

  • 使流管道更加靈活和富有表現力。
  • 儘可能允許自定義中間操作來操作無限大小的流。

流(Stream)::gather(Gatherer) 是一種新的中間流操作,透過應用使用者自定義實體——收集器(Gatherer)來處理流中的元素。利用gather操作,我們可以構建高效且適用於並行處理的流,實現幾乎所有的中間操作。Stream::gather(Gatherer) 在中間操作中的作用類似於Stream::collect(Collector)在終止操作中的作用。

Gatherer用於表示對流中元素的轉換,它是java.util.stream.Gatherer介面的一個例項。Gatherer可以以一對一、一對多、多對一或多對多的方式轉換元素。它可以跟蹤已處理過的元素以影響後續元素的轉換,支援短路操作以將無限流轉換為有限流,並能啟用並行執行。例如,一個Gatherer可以從輸入流中按條件轉換一個輸入元素為一個輸出元素,直到某一條件變為真,此時開始將一個輸入元素轉換為兩個輸出元素。

Gatherer由四個協同工作的函式定義:

  1. 可選初始化函式提供了在處理流元素過程中維持私有狀態的物件。例如,Gatherer可以儲存當前元素,以便下次應用時比較新元素和前一個元素,並僅輸出兩者中較大的那個。實際上,這種Gatherer將兩個輸入元素轉換為一個輸出元素。

  2. 整合函式整合來自輸入流的新元素,可能檢查私有狀態物件,並可能向輸出流發出元素。它還可以在到達輸入流末尾之前提前終止處理;例如,一個搜尋整數流中最大值的Gatherer在檢測到Integer.MAX_VALUE時可以終止處理。

  3. 可選組合函式可用於在輸入流標記為並行時並行評估Gatherer。若Gatherer不支援並行,則仍可作為並行流管道的一部分,但在評估時將以順序方式進行。這對於某些本質上有序因而無法並行化的操作場景非常有用。

  4. 可選完成函式在沒有更多輸入元素要消費時被呼叫。該函式可以檢查私有狀態物件,並可能發出額外的輸出元素。例如,在輸入元素中搜尋特定元素的Gatherer在其完成器被呼叫時,若未能找到目標元素,可以透過丟擲異常等方式報告失敗。

當呼叫Stream::gather時,執行以下等效步驟:

  1. 建立一個Downstream物件,當接收到Gatherer輸出型別的元素時,將其傳遞到管道中的下一階段。

  2. 透過呼叫其initializer的get()方法獲取Gatherer的私有狀態物件。

  3. 透過呼叫其integrator()方法獲取Gatherer的整合器。

  4. 當存在更多輸入元素時,呼叫整合器的integrate(...)方法,傳入狀態物件、下一個元素和下游物件。若該方法返回false,則終止處理。

  5. 獲取Gatherer的完成器並使用狀態物件和下游物件對其呼叫。

現有Stream介面中宣告的所有中間操作都可以透過呼叫帶有實現該操作的Gatherer的gather方法來實現。例如,對於一個T型別元素的流,Stream::map透過應用一個函式將每個T元素轉換為U元素並將其向下傳遞;這實質上就是一個無狀態的一對一Gatherer。另一個例子是Stream::filter,它採用一個謂詞決定輸入元素是否應向下傳遞;這只是一個無狀態的一對多Gatherer。事實上,從概念上講,每一個流管道都可以等同於:

source.gather(...).gather(...).gather(...).collect(...)

Structured Concurrency - JEP 462:

結構化併發。簡化了併發程式設計。結構化併發將在不同執行緒中執行的相關任務組視為單個工作單元,從而簡化了錯誤處理和取消,提高了可靠性並增強了可觀察性。

最佳化:

  1. 促進一種併發程式設計風格,能夠消除由於取消和關閉產生的常見風險,例如執行緒洩露和取消延遲。
  2. 提升併發程式碼的可觀測性。

結構化併發API的主要類是java.util.concurrent包中的StructuredTaskScope類。此類允許開發人員將任務結構化為一組併發子任務,並將它們作為一個整體進行協調管理。子任務透過分別建立新執行緒(fork)並在之後作為一個整體等待它們完成(join)和可能的情況下作為一個整體取消它們。子任務的成功結果或異常將被父任務聚合並處理。StructuredTaskScope確保了子任務的生命週期被限定在一個清晰的詞法作用域內,在這個作用域內,任務與其子任務的所有互動,包括分叉(fork)、同步(join)、取消(cancellation)、錯誤處理和結果合成都在此發生。

使用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()            // 同步兩個子任務
             .throwIfFailed();  // 並傳播錯誤資訊

        // 這裡,兩個子任務都已經成功,所以組合它們的結果
        return new Response(user.get(), order.get());
    }
}

這裡理解涉及執行緒的生命週期變得簡單:在所有情況下,它們的生命週期都被限制在一個詞法作用域內,即try-with-resources語句的主體內。此外,使用StructuredTaskScope確保了一系列有價值的特性:

  1. 錯誤處理與短路機制——如果findUser()fetchOrder()子任務之一失敗,尚未完成的另一個子任務將被取消。(這是由ShutdownOnFailure實現的關閉策略管理的,也有可能實現其他策略)。

  2. 取消傳播——如果在呼叫join()之前或期間執行handle()方法的執行緒被中斷,則當執行緒退出作用域時,兩個子任務都將自動取消。

  3. 清晰性——上述程式碼具有清晰的結構:設定子任務,等待它們完成或被取消,然後決定是否成功(並處理已完成子任務的結果)或失敗(子任務已經結束,因此無需進一步清理)。

  4. 可觀測性——如下面所述,執行緒轉儲能夠清晰地顯示任務層級關係,執行findUser()fetchOrder()的執行緒在轉儲中顯示為scope的子執行緒。

Scoped Values - JEP 464:

作用域值最佳化。線上程內和跨執行緒之間有效共享不可變資料。這個Java增強它旨在提供一種機制,允許開發者在Java應用程式中安全地線上程內部以及跨執行緒之間共享不可變資料。該特性旨在替代或改善現有的ThreadLocal機制,提供一種更加可控、易於管理和高效的解決方案,尤其是在涉及大規模併發處理和跨層資料傳遞場景時。透過範圍值,開發人員可以更好地組織和管理在特定作用域內有效的變數,同時確保資源的有效利用和資料的安全共享。

最佳化:

  • 易用性 — 理解資料流應當輕鬆直觀。
  • 可理解性 — 共享資料的生命週期可以從程式碼的語法結構中清晰可見。
  • 健壯性 — 呼叫者共享的資料只能被合法的被呼叫者獲取。
  • 效能 — 資料能夠有效地在大量執行緒間高效共享。

作用域值是一種容器物件,允許方法在同一個執行緒內安全高效地將其資料值與直接和間接的被呼叫者共享,同時也能與子執行緒共享,而無需依賴方法引數。它是一個型別為ScopedValue的變數,通常被宣告為final static欄位,並設定為private訪問許可權,以防止其他類的程式碼直接訪問。

類似執行緒區域性變數,作用域值關聯了多個值,每個執行緒對應一個。具體使用的值取決於哪個執行緒呼叫了它的方法。不同於執行緒區域性變數,範圍限定值只被寫入一次,並且線上程執行期間只能在一定時間段內可用。

作用域值的使用如下所示。一些程式碼呼叫ScopedValue.where,提供一個範圍限定值及其要繫結的物件。呼叫run方法會繫結該範圍限定值,為當前執行緒提供一個特定的副本,然後執行作為引數傳遞的lambda表示式。在run方法執行期間,lambda表示式或從中直接或間接呼叫的任何方法,都可以透過值的get()方法讀取範圍限定值。當run方法執行完畢後,該繫結關係會被銷燬。

final static ScopedValue<...> NAME = ScopedValue.newInstance();

// 在某個方法中
ScopedValue.where(NAME, <value>)
           .run(() -> { ... NAME.get() ... 呼叫方法 ... });

// 在lambda表示式中直接或間接呼叫的方法中
... NAME.get() ...

程式碼的結構明確了執行緒可以讀取其作用域值副本的時間段。這個有限的生命週期極大地簡化了對執行緒行為的推理。資料從呼叫者單向傳輸至直接和間接的被呼叫者,一眼就能看出。不存在能讓遠端程式碼隨時改變範圍限定值的set方法。這也有助於提高效能:無論呼叫者和被呼叫者的棧距離如何,透過get()方法讀取作用域值的速度常常與讀取區域性變數一樣快。

Vector API - JEP 460:

向量API。一個能夠在支援的CPU架構上執行時可靠編譯為最優向量指令的API,從而實現優於等效標量計算的效能。
本JEP提議在JDK 22中重新孵化該API,相比於JDK 21版本,API進行了些許增強。實現內容包括bug修復和效能最佳化。主要變更如下:
支援透過任意原始元素型別的陣列支援的堆記憶體MemorySegments進行向量訪問。在此之前,訪問僅限於由位元組陣列支援的堆記憶體MemorySegments

最佳化:

  1. 清晰簡潔的API
    API應該能夠清晰簡潔地表述一系列由迴圈內向量操作序列組成的各種向量計算,可能還包括控制流程。應支援針對向量大小或每向量的通道數進行泛型表達,從而使這類計算能夠在支援不同向量大小的硬體之間移植。

  2. 平臺無關性
    API應獨立於CPU架構,支援在多種支援向量指令的架構上實現。按照Java API的一貫原則,在平臺最佳化和可移植性產生衝突時,我們會傾向於使API更具可移植性,即使這意味著某些特定於平臺的慣用法無法在便攜程式碼中表達。

  3. 在x64和AArch64架構上的可靠執行時編譯和高效能
    在具備能力的x64架構上,Java執行時環境,特別是HotSpot C2編譯器,應將向量操作編譯為相應的高效向量指令,比如Streaming SIMD Extensions (SSE) 和Advanced Vector Extensions (AVX)支援的那些指令。開發者應有信心他們所表達的向量操作將可靠地緊密對映到相關的向量指令上。在具備能力的ARM AArch64架構上,C2同樣會將向量操作編譯為NEON和SVE支援的向量指令。

  4. 優雅降級
    有時向量計算可能無法完全在執行時表述為向量指令序列,可能是因為架構不支援所需的某些指令。在這種情況下,Vector API實現應能夠優雅降級並仍然正常運作。這可能包括在向量計算無法高效編譯為向量指令時發出警告。在不支援向量的平臺上,優雅降級將生成與手動展開迴圈相競爭的程式碼,其中展開因子為所選向量的通道數。

  5. Project Valhalla專案的契合
    Vector API的長期目標是利用Project Valhalla對Java物件模型的增強功能。主要來說,這意味著將Vector API當前基於值的類更改為值類,以便程式能夠處理值物件,即缺乏物件標識性的類例項。因此,Vector API將在多個版本中孵化,直至Project Valhalla的必要特性作為預覽功能可用。一旦這些Valhalla特性可用,我們將調整Vector API及其實現以使用這些特性,並將Vector API本身提升為預覽功能。

向量由抽象類Vector<E>表示。型別變數E被例項化為向量覆蓋的標量基本整數或浮點元素型別的裝箱型別。一個向量還具有形狀屬性,該屬性定義了向量的大小(以位為單位)。當向量計算由HotSpot C2編譯器編譯時,向量的形狀決定了Vector<E>例項如何對映到硬體向量暫存器。向量的長度,即車道數或元素個數,等於向量大小除以元素大小。

支援的一系列元素型別(E)包括Byte、Short、Integer、Long、Float和Double,分別對應於標量基本型別byte、short、int、long、float和double。

支援的一系列形狀對應於64位、128位、256位和512位的向量大小,以及最大位數。512位形狀可以將位元組打包成64個車道,或者將整數打包成16個車道,具有這種形狀的向量可以一次性操作64個位元組或16個整數。max-bits形狀支援當前架構的最大向量尺寸,這使得API能夠支援ARM SVE平臺,該平臺實現可以支援從128位到2048位,以128位為增量的任何固定尺寸。

效能上的改進

Regional Pinning for G1 - JEP 423:

區域固定。透過在G1中實現區域固定(regional pinning),從而在Java Native Interface (JNI) 臨界區域內不需要禁用垃圾收集,以此來減少延遲。

最佳化:

  1. 不會因JNI臨界區域導致執行緒停滯。
  2. 不會因JNI臨界區域而導致垃圾收集啟動時增加額外延遲。
  3. 當沒有JNI臨界區域活動時,GC暫停時間不會出現倒退。
  4. 當JNI臨界區域活動時,GC暫停時間只會有最小程度的倒退。

工具類

Launch Multi-File Source-Code Programs - JEP 458:

啟動多檔案原始碼程式。允許使用者在不首先編譯程式的情況下執行由多個 Java 原始碼檔案提供的程式。

最佳化:

  1. 透過使從小型程式向大型程式的過渡更加漸進,使開發人員能夠選擇何時以及何時費力地配置構建工具,提高了開發人員的生產力。

Java22增強了Java啟動器的原始檔模式,使其能夠執行以多份Java原始碼檔案形式提供的程式。

舉例來說,假設一個目錄包含了兩個檔案: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將會在記憶體中編譯Prog類並呼叫其main方法。由於Prog類中的程式碼引用了Helper類,啟動器會在檔案系統中查詢Helper.java檔案,並在記憶體中編譯Helper類。如果Helper類中的程式碼又引用了其他類,例如HelperAux類,那麼啟動器還會找到HelperAux.java並對其進行編譯。

當不同.java檔案中的類互相引用時,Java啟動器並不保證按照特定順序或時間點編譯這些.java檔案。例如,啟動器可能先編譯Helper.java再編譯Prog.java。有些程式碼可能在程式開始執行前就已經被編譯,而其他程式碼可能在需要時才懶載入編譯。

只有被程式引用到的類所在的.java檔案才會被編譯。這樣一來,開發者可以在嘗試新版本程式碼時不必擔心舊版本會被意外編譯。例如,假設目錄中還包含OldProg.java檔案,其中包含Progr類的一箇舊版本,該版本期望Helper類有一個名為go的方法而不是run方法。當執行Prog.java時,存在包含潛在錯誤的OldProg.java檔案並不會影響程式執行。

一個.java檔案中可以宣告多個類,且會被一起編譯。在一個.java檔案中共宣告的類優先於在其他.java檔案中宣告的類。例如,假設上面的Prog.java檔案擴充套件後也在其中宣告瞭Helper類,儘管Helper.java檔案中已有一個同名類。當Prog.java中的程式碼引用Helper時,會使用在Prog.java中共同宣告的那個類;啟動器不會去搜尋Helper.java檔案。

原始碼程式中禁止重複的類宣告。也就是說,同一個.java檔案內或構成程式的不同.java檔案之間的類宣告,如果名稱相同,則不允許存在。假設經過編輯後,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類,呼叫Prog類的main方法,然後——由於main方法引用了Helper——查詢並編譯Helper.java中的Helper和Aux類。Helper.java中對Aux類的重複宣告是不允許的,所以程式會停止執行,啟動器報告錯誤。

當透過Java啟動器傳遞單個.java檔名稱時,就會觸發其原始檔模式。如果提供了額外的檔名,它們會成為主方法的引數。例如,執行命令java Prog.java Helper.java會導致字串陣列"Helper.java"作為引數傳給Prog類的main方法。

其他特性

除了JEP中描述的更改之外,發行說明中還列出了許多較小的更新,這些更新對許多應用程式開發者有重要意義。其中包括廢棄過時的API和移除先前已經棄用的API。

  1. 向keytool和jarsigner新增了更多演算法。
  2. 垃圾回收器吞吐量方面的改進,特別是在“年輕代”垃圾回收方面。
  3. 改進了系統模組描述符的版本報告功能。
  4. 提高了對原生程式碼“等待”處理選項的完善。
  5. Unicode通用區域資料倉儲已更新至版本44。
  6. 支援從位元組碼載入的型別上的型別註解。
  7. ForkJoinPool和ForkJoinTask現在能更好地處理不可中斷任務。
  8. 對客戶端與伺服器TLS連線屬性配置提供了更多的靈活性。
  9. 提高了對原生記憶體跟蹤的功能,包括峰值使用情況的報告。
  10. 最後,如同所有特性發布版一樣,JDK 22包含了數百項效能、穩定性和安全性更新,包括適應底層作業系統和韌體更新及標準變化。使用者和應用程式開發者通常在不知不覺中受益於這些變化。

最後,JDK 22是透過六個月的釋出節奏按時交付的13th功能版本。由於預期改進源源不斷,這種程度的可預測性使開發人員能夠輕鬆管理創新的採用。Oracle 不會為 JDK 22 提供長期支援,在 2023 年 9 月之前提供更新,之後它將被 Oracle JDK 23 取代。最近的長期維護版本是Java 21。

image.png

本文已收錄於我的個人部落格:碼農Academy的部落格,專注分享Java技術乾貨,包括Java基礎、Spring Boot、Spring Cloud、Mysql、Redis、Elasticsearch、中介軟體、架構設計、面試題、程式設計師攻略等

相關文章