聊聊 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 (孵化)

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