聊聊 Java 19 新功能,你學會了嗎?

語言: CN / TW / HK

​Java 19 在2022 年 9 月 20 日正式釋出,Java 19 不是一個長期支援版本,直到 2023 年 3 月它將被 JDK 20 取代,這次更新共帶來 7 個新功能。

➜  bin ./java -version
openjdk version "19" 2022-09-20
OpenJDK Runtime Environment (build 19+36-2238)
OpenJDK 64-Bit Server VM (build 19+36-2238, mixed mode, sharing)

OpenJDK Java 19 下載:https://jdk.java.net/19/

OpenJDK Java 19 文件:https://openjdk.java.net/projects/jdk/19/

Java 19 帶來的 7 個新特性:

JEP

描述

405

Record 模式匹配 (Preview)

425

虛擬執行緒 (預覽)

427

Switch 模式匹配 (三次預覽)

422

Linux/RISC-V Port

426

Vector API (四次孵化)

424

外部函式 & 記憶體 API (Preview)

428

Structured Concurrency (Incubator)

Java 19 新功能介紹是 Java 新特性系列文章中的一部分。系列詳情可以瀏覽:https://www.wdbyte.com/java-feature/

JEP 405: Record 模式匹配(預覽)

record​ 是一種全新的型別,它本質上是一個 final​ 類,同時所有的屬性都是 final​ 修飾,它會自動編譯出 public get hashcode​ 、equals、toString 等方法,減少了程式碼編寫量。Record 在 Java 14 中被提出,在 Java 15 中二次預覽,在 Java 16 中正式釋出。

示例:編寫一個 Dog record 類,定義 name 和 age 屬性。

package com.wdbyte;

public record Dog(String name, Integer age) {
}

Record 的使用。

package com.wdbyte;

public class Java14Record {

    public static void main(String[] args) {
        Dog dog1 = new Dog("牧羊犬", 1);
        Dog dog2 = new Dog("田園犬", 2);
        Dog dog3 = new Dog("哈士奇", 3);
        System.out.println(dog1);
        System.out.println(dog2);
        System.out.println(dog3);
    }
}

輸出結果:

Dog[name=牧羊犬, age=1]
Dog[name=田園犬, age=2]
Dog[name=哈士奇, age=3]

在 Java 19 中,為 Record 帶來了增強的模式匹配,在使用 instanceof 後,可以進行型別轉換。

public class RecordTest {
    public static void main(String[] args) {
        Object dog1 = new Dog("牧羊犬", 1);
        if(dog1 instanceof Dog dogTemp){
            System.out.println(dogTemp.name());
         }
    }
}
record Dog( String name, Integer age ){
}

甚至可以在使用 instanceof​ 時直接得到 Record 中的變數引用。

public class RecordTest2 {

    public static void main(String[] args) {
        Object dog1 = new Dog("牧羊犬", 1);
        if(dog1 instanceof Dog(String name,Integer age)){
            System.out.println(name+":"+age);

         }
    }
}

record Dog( String name, Integer age ){
}

擴充套件:

Java 14 instanceof 型別推斷

Java 16 Record 介紹

JEP 425: 虛擬執行緒 (預覽)

很實用的一個新特性,從 Java 19 開始逐步的引入虛擬執行緒,虛擬執行緒是輕量級的執行緒,可以在顯著的減少程式碼的編寫,提高可維護性的同時提高系統的吞吐量。

引入的原因

一直以來,在 Java 併發程式設計中,Thread 都是十分重要的一部分,Thread 是 Java 中的併發單元,每個 Thread 執行緒都提供了一個堆疊來儲存區域性變數和方法呼叫,以及執行緒上下文等相關資訊。

但問題是執行緒和程序一樣,都是一項昂貴的資源,JDK 將 Thread 執行緒實現為作業系統執行緒的包裝器,也就是說成本很高,而且數量有限。也因此我們會使用執行緒池來管理執行緒,同時限制執行緒的數量。比如常用的 Tomcat 會為每次請求單獨使用一個執行緒進行請求處理,同時限制處理請求的執行緒數量以防止執行緒過多而崩潰;這很有可能在 CPU 或網路連線沒有耗盡之前,執行緒數量已經耗盡,從而限制了 web 服務的吞吐量。

看到這裡你可能要說了,可以放棄請求和執行緒一一對應的方式啊,使用非同步程式設計來解決這個問題,把請求處理分段,在組合成順序管道,通過一套 API 進行管理,這樣就可以使用有限的執行緒來處理超過執行緒數量的請求。這當然也是可以的,但是隨之而來的問題是:

  • 需要額外的學習非同步程式設計。
  • 程式碼複雜度增加,等於放棄了語言的基本順序組合運算。
  • 堆疊上下文資訊都變得難以追蹤。
  • Debug 困難。
  • 和 Java 平臺本身的程式設計風格有衝突,Java 併發單元是 Thread,而這時是非同步管道。

虛擬執行緒

基於上面的種種原因,Java 19 引入了虛擬執行緒,在使用體驗上和 Thread 沒有區別,與之前的 API 互相相容,但是相比之下虛擬執行緒資源佔用非常少,同時優化了硬體的使用效率,因此非常易用且不需要被池化。

下面是一個示例,建立 10 萬個執行緒,然後都休眠 1 秒鐘最後列印耗時,如果是開傳統的 Thread 執行緒的方式,資源十分緊張;如果是執行緒池的方式,必定有部分執行緒在等待執行緒釋放;但是使用虛擬執行緒的方式,可以瞬間完成。

import java.util.concurrent.Executors;
import java.util.stream.IntStream;

public class ThreadTest {

    public static void main(String[] args) {
        long start = System.currentTimeMillis();
        try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
            IntStream.range(0, 100_000).forEach(i -> {
                executor.submit(() -> {
                    Thread.sleep(1000);
                    return i;
                });
            });
        } 
        
        System.out.println("耗時:" + (System.currentTimeMillis() - start)+"ms");
    }
}

執行後發現 1.3 秒執行完畢,速度驚人。

➜  bin ./java --enable-preview --source 19  ThreadTest.java
注: ThreadTest.java 使用 Java SE 19 的預覽功能。
注: 有關詳細資訊,請使用 -Xlint:preview 重新編譯。
耗時:1309ms
➜  bin

注意:虛擬執行緒只是增加程式的吞吐量,並不能提高程式的處理速度。

JEP 427: switch 模式匹配 (三次預覽)

Switch 模式匹配在 Java 17 中已經引入,在 Java 18 中二次預覽,現在在 Java 19 中進行三次預覽,功能和在 Java 18 新功能介紹 - Switch 中介紹的一樣,改進後的 Switch 模式匹配可以程式碼更加簡潔,邏輯更加清晰,下面是一些使用示例對比。

下面是幾個例子:

static String formatter(Object o) {
    String formatted = "unknown";
    if (o instanceof Integer i) {
        formatted = String.format("int %d", i);
    } else if (o instanceof Long l) {
        formatted = String.format("long %d", l);
    } else if (o instanceof Double d) {
        formatted = String.format("double %f", d);
    } else if (o instanceof String s) {
        formatted = String.format("String %s", s);
    }
    return formatted;
}

而在 Java 17 之後,可以通過下面的寫法進行改進:

static String formatterPatternSwitch(Object o) {
    return switch (o) {
        case Integer i -> String.format("int %d", i);
        case Long l    -> String.format("long %d", l);
        case Double d  -> String.format("double %f", d);
        case String s  -> String.format("String %s", s);
        default        -> o.toString();
    };
}

switch 可以和 null 進行結合判斷:

static void testFooBar(String s) {
    switch (s) {
        case null         -> System.out.println("Oops");
        case "Foo", "Bar" -> System.out.println("Great");
        default           -> System.out.println("Ok");
    }
}

case 時可以加入複雜表示式:

static void testTriangle(Shape s) {
    switch (s) {
        case Triangle t && (t.calculateArea() > 100) ->
            System.out.println("Large triangle");
        default ->
            System.out.println("A shape, possibly a small triangle");
    }
}

case 時可以進行型別判斷:

sealed interface S permits A, B, C {}
final class A implements S {}
final class B implements S {}
record C(int i) implements S {}  // Implicitly final

static int testSealedExhaustive(S s) {
    return switch (s) {
        case A a -> 1;
        case B b -> 2;
        case C c -> 3;
    };
}

擴充套件:JEP 406:Switch 的型別匹配(預覽)

JEP 422: Linux/RISC-V Port

RISC-V是一個免費和開源的 RISC 指令集架構 (ISA),實際上 RISC-V 是一系列相關的 ISA,現在 Java 19 開始對其進行支援。

JEP 424: 外部函式 & 記憶體 API (預覽)

此功能引入的 API 允許 Java 開發者與 JVM 之外的程式碼和資料進行互動,通過呼叫外部函式(JVM之外)和安全的訪問外部記憶體(非 JVM 管理),讓 Java 程式可以呼叫本機庫並處理本機資料,而不會像 JNI 一樣存在很多安全風險。

這不是一個新功能,自 Java 14 就已經引入,此次對其進行了效能、通用性、安全性、易用性上的優化。

歷史

  • Java 14JEP 370 引入了外部記憶體訪問 API(孵化器)。
  • Java 15JEP 383引入了外部記憶體訪問 API(第二孵化器)。
  • Java 16JEP 389引入了外部連結器 API(孵化器)。
  • Java 16JEP 393引入了外部記憶體訪問 API(第三孵化器)。
  • Java 17JEP 412引入了外部函式和記憶體 API(孵化器)。
  • Java 18JEP 419引入了外部函式和記憶體 API(二次孵化器)。

其他更新

JEP 426: Vector API (四次孵化)

通過將在執行時可靠地編譯為支援的 CPU 架構上的向量指令的向量計算表示,與等效的標量計算相比,實現了卓越的效能。此功能已經第四次孵化,在之前 Java 16 ~ Java 18 中都有介紹,這裡不做贅述。

JEP 428: Structured Concurrency (孵化)

通過簡化多執行緒程式設計並將在不同執行緒中執行的多個任務視為單個工作單元,簡化錯誤處理和取消,提高可靠性並增強可觀察性。