Java SE 19 新增特性

Java SE 19 新增特性

作者:Grey

原文地址:

博客園:Java SE 19 新增特性

CSDN:Java SE 19 新增特性

源碼

源倉庫: Github:java_new_features

鏡像倉庫: GitCode:java_new_features

HashMap 新的構造方法

Java SE 19,構造哈希表的時候,由於有擴容因子 0.75 的設置,所以如果要開闢一個 120 空間的哈希表,需要如下定義

Map<Integer,Integer> map1 = new HashMap<>(160);

Java SE 19 中,HashMap 有了新的構造方法,可以用 newHashMap 直接指定具體大小,不需要提前做換算。

這個用法類似 Guava 的集合構造方式。

如上例,可以使用

Map<Integer, Integer> map2 = HashMap.newHashMap(120);

代碼如下

import java.util.*;
public class NewHashMapMethodTest {
    public static void main(String[] arg) {
        // jdk 19之前
        // 由於有 擴容因子 0.75 的設置,所以如果要開闢一個120的哈希表,需要如下定義
        Map<Integer,Integer> map1 = new HashMap<>(160);
        for (int i = 0; i < 10; i++) {
            map1.put(i,i);
        }
        System.out.println(map1);
        // jdk 19及以後
        // 可以用newHashMap直接指定具體大小,不需要提前做換算
        Map<Integer, Integer> map2 = HashMap.newHashMap(120);
        for (int i = 0; i < 10; i++) {
            map2.put(i,i);
        }
        System.out.println(map2);
    } 
}

switch 類型匹配增強(第三次預覽)

首次引入這個功能是在Java SE 17

switch (obj) {
  case String s && s.length() > 5 -> System.out.println(s.toUpperCase())。
  ...
}

我們可以在 switch 語句中檢查一個對象是否屬於某個特定的類,以及它是否有額外的特徵(比如在例子中:長於五個字符)。

在 Java SE 19 中,我們必須使用新的關鍵字 when 來代替 &&

完整代碼如下

package git.snippets.jdk19;

/**
 * switch 增強 第三次預覽
 * 需要增加 --enable-preview參數
 *
 * @author <a href="mailto:[email protected]">Grey</a>
 * @date 2022/9/22
 * @since 19
 */
public class SwitchEnhancedTest {
    public static void main(String[] args) {
        checkObjSince19("hello world");
    }

    public static void checkObjSince19(Object when) {
        // when 是一個所謂的 "上下文關鍵字",因此只在一個 case 標籤中具有意義。如果你的代碼中有名稱為 "when "的變量或方法,你不需要改變它們。
        switch (when) {
            case String s when s.length() > 5 -> System.out.println(s.toUpperCase());
            case String s -> System.out.println(s.toLowerCase());
            case Integer i -> System.out.println(i * i);
            default -> {
            }
        }
    }
}

when 是一個所謂的 “上下文關鍵字”,因此只在一個 case 標籤中具有意義。如果你的代碼中有名稱為 “when “的變量或方法,你不需要改變它們。

record 的匹配增強(預覽功能)

switch 和 instanceof 的增強匹配(Java SE 16 新增特性)功能現在可以用於 record,示例代碼如下

package git.snippets.jdk19;

/**
 * record 模式匹配增強
 * 需要增加 --enable-preview參數
 *
 * @author <a href="mailto:[email protected]">Grey</a>
 * @date 2022/9/22
 * @since 19
 */
public class RecordTest {
    public static void main(String[] args) {
        Points points = new Points(1, 2);
        Line line = new Line(new Points(1, 2), new Points(3, 4));
        printPoints(points);
        printLine(line);
    }


    private static void printPoints(Object object) {
        if (object instanceof Points(int x,int y)) {
            System.out.println("jdk 19 object is a position, x = " + x + ", y = " + y);
        }
        if (object instanceof Points points) {
            System.out.println("pre jdk 19 object is a position, x = " + points.x()
                    + ", y = " + points.y());
        }
        switch (object) {
            case Points position -> System.out.println("pre jdk 19 object is a position, x = " + position.x()
                    + ", y = " + position.y());
            default -> throw new IllegalStateException("Unexpected value: " + object);
        }
        switch (object) {
            case Points(int x,int y) -> System.out.println(" jdk 19 object is a position, x = " + x
                    + ", y = " + y);
            default -> throw new IllegalStateException("Unexpected value: " + object);
        }

    }

    public static void printLine(Object object) {
        if (object instanceof Line(Points(int x1,int y1),Points(int x2,int y2))) {
            System.out.println("object is a path, x1 = " + x1 + ", y1 = " + y1
                    + ", x2 = " + x2 + ", y2 = " + y2);
        }
        switch (object) {
            case Line(Points(int x1,int y1),Points(int x2,int y2)) ->
                    System.out.println("object is a path, x1 = " + x1 + ", y1 = " + y1
                            + ", x2 = " + x2 + ", y2 = " + y2);
            // other cases ...
            default -> throw new IllegalStateException("Unexpected value: " + object);
        }
    }

}

record Points(int x, int y) {
}

record Line(Points from, Points to) {
}

虛擬線程(預覽功能)

虛擬線程在Project Loom中已經開發了好幾年,到目前為止只能用自編譯的JDK進行測試。

具體可以查看這篇文章:Virtual Threads in Java (Project Loom)

Foreign Function 和 Memory API (預覽功能)

Project Panama中,取代繁瑣、易出錯、速度慢的 Java 本地接口(JNI)的工作已經進行了很長時間。

在 Java 14 和 Java 16 中已經引入了 “外來內存訪問 API “和 “外來鏈接器 API”–最初都是單獨處於孵化階段。在 Java 17 中,這些 API 被合併為 “Foreign Function & Memory API”(FFM API),直到 Java 18,它一直處於孵化階段。

在 Java 19 中,JDK Enhancement Proposal 424最終將新的 API 提升到了預覽階段,

FFM API 可以直接從 Java 訪問本地內存(即 Java 堆外的內存)和訪問本地代碼(如 C 庫)。

下面是一個簡單的例子,它在堆外內存中存儲一個字符串,並對其調用 C 語言標準庫的 “strlen “函數。

package git.snippets.jdk19;

import java.lang.foreign.FunctionDescriptor;
import java.lang.foreign.Linker;
import java.lang.foreign.MemorySegment;
import java.lang.foreign.SymbolLookup;
import java.lang.invoke.MethodHandle;

import static java.lang.foreign.SegmentAllocator.implicitAllocator;
import static java.lang.foreign.ValueLayout.ADDRESS;
import static java.lang.foreign.ValueLayout.JAVA_LONG;

/**
 * FFM API
 * @author <a href="mailto:[email protected]">Grey</a>
 * @date 2022/9/22
 * @since 19
 */
public class FFMTest {
    public static void main(String[] args) throws Throwable {
        // 1. Get a lookup object for commonly used libraries
        SymbolLookup stdlib = Linker.nativeLinker().defaultLookup();

        // 2. Get a handle to the "strlen" function in the C standard library
        MethodHandle strlen = Linker.nativeLinker().downcallHandle(
                stdlib.lookup("strlen").orElseThrow(),
                FunctionDescriptor.of(JAVA_LONG, ADDRESS));

        // 3. Convert Java String to C string and store it in off-heap memory
        MemorySegment str = implicitAllocator().allocateUtf8String("Happy Coding!");

        // 4. Invoke the foreign function
        long len = (long) strlen.invoke(str);

        System.out.println("len = " + len);
    }
}

結構化並發(孵化器)

如果一個任務由不同的子任務組成,可以並行完成(例如,從數據庫訪問數據、調用遠程 API 和加載文件),我們可以使用 Java 多線程的一些工具類來完成。

例如:

    private final ExecutorService executor = Executors.newCachedThreadPool();

    // jdk 19 之前
    public Invoice createInvoice(int orderId, int customerId, String language) throws ExecutionException, InterruptedException {
        Future<Order> orderFuture = executor.submit(() -> loadOrderFromOrderService(orderId));

        Future<Customer> customerFuture = executor.submit(() -> loadCustomerFromDatabase(customerId));

        Future<String> invoiceTemplateFuture = executor.submit(() -> loadInvoiceTemplateFromFile(language));

        Order order = orderFuture.get();
        Customer customer = customerFuture.get();
        String invoiceTemplate = invoiceTemplateFuture.get();

        return Invoice.generate(order, customer, invoiceTemplate);
    }

但是:

如果一個子任務發生錯誤–我們如何取消其他子任務?

如果某個子任務不需要了,我們如何取消這個子任務呢?

這兩種情況都可以,但需要相當複雜和難以維護的代碼。

而如果我們想對這種類型的並發代碼進行調試也非常麻煩。

JDK Enhancement Proposal 428為所謂的 “結構化並發 “引入了一個 API,這個概念旨在改善這種類型需求的代碼的實現、可讀性和可維護性。

使用 StructuredTaskScope,我們可以把這個例子改寫成如下。

    public Invoice createInvoiceSinceJava19(int orderId, int customerId, String language)
            throws ExecutionException, InterruptedException {
        try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {
            Future<Order> orderFuture =
                    scope.fork(() -> loadOrderFromOrderService(orderId));

            Future<Customer> customerFuture =
                    scope.fork(() -> loadCustomerFromDatabase(customerId));

            Future<String> invoiceTemplateFuture =
                    scope.fork(() -> loadInvoiceTemplateFromFile(language));

            scope.join();
            scope.throwIfFailed();

            Order order = orderFuture.resultNow();
            Customer customer = customerFuture.resultNow();
            String invoiceTemplate = invoiceTemplateFuture.resultNow();

            return new Invoice(order, customer, invoiceTemplate);
        }
    }

使用 StructuredTaskScope,我們可以將 executor.submit() 替換為 scope.fork()

使用 scope.join(),我們等待所有任務完成–或者至少有一個任務失敗或被取消。在後兩種情況下,隨後的 throwIfFailed() 會拋出一個 ExecutionException 或一個 CancellationException

與舊方法相比,新方法帶來了以下改進。

  1. 任務和子任務在代碼中形成一個獨立的單元,每個子任務都在一個新的虛擬線程中執行。

  2. 一旦其中一個子任務發生錯誤,所有其他子任務都會被取消。

  3. 當調用線程被取消時,子任務也會被取消。

完整代碼如下:

package git.snippets.jdk19;

import jdk.incubator.concurrent.StructuredTaskScope;

import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

/**
 * 預覽功能
 * 控制台運行
 * 1. 配置Java運行環境是JDK 19
 * 2. 注釋掉 package 路徑
 * 3. 在本代碼的目錄下執行
 * 編譯:javac --enable-preview -source 19 --add-modules jdk.incubator.concurrent StructuredConcurrencyTest.java
 *運行:java --enable-preview --add-modules jdk.incubator.concurrent StructuredConcurrencyTest
 * @author <a href="mailto:[email protected]">Grey</a>
 * @date 2022/9/22
 * @since 19
 */
public class StructuredConcurrencyTest {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        new StructuredConcurrencyTest().createInvoiceSinceJava19(1, 2, "ZH");
    }

    private final ExecutorService executor = Executors.newCachedThreadPool();

    // jdk 19 之前
    public Invoice createInvoice(int orderId, int customerId, String language) throws ExecutionException, InterruptedException {
        Future<Order> orderFuture = executor.submit(() -> loadOrderFromOrderService(orderId));

        Future<Customer> customerFuture = executor.submit(() -> loadCustomerFromDatabase(customerId));

        Future<String> invoiceTemplateFuture = executor.submit(() -> loadInvoiceTemplateFromFile(language));

        Order order = orderFuture.get();
        Customer customer = customerFuture.get();
        String invoiceTemplate = invoiceTemplateFuture.get();

        return Invoice.generate(order, customer, invoiceTemplate);
    }


    // jdk 19 之後
    public Invoice createInvoiceSinceJava19(int orderId, int customerId, String language)
            throws ExecutionException, InterruptedException {
        try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {
            Future<Order> orderFuture =
                    scope.fork(() -> loadOrderFromOrderService(orderId));

            Future<Customer> customerFuture =
                    scope.fork(() -> loadCustomerFromDatabase(customerId));

            Future<String> invoiceTemplateFuture =
                    scope.fork(() -> loadInvoiceTemplateFromFile(language));

            scope.join();
            scope.throwIfFailed();

            Order order = orderFuture.resultNow();
            Customer customer = customerFuture.resultNow();
            String invoiceTemplate = invoiceTemplateFuture.resultNow();

            return new Invoice(order, customer, invoiceTemplate);
        }
    }

    private String loadInvoiceTemplateFromFile(String language) {
        return language;
    }

    private Customer loadCustomerFromDatabase(int customerId) {
        return new Customer(customerId);
    }

    private Order loadOrderFromOrderService(int orderId) {
        return new Order(orderId);
    }
}

class Invoice {
    // TODO
    public Invoice(Order order, Customer customer, String invoiceTemplate) {

    }

    public static Invoice generate(Order order, Customer customer, String invoiceTemplate) {
        return null;
    }
}

class Order {
    private int id;

    public Order(int orderId) {
        this.id = orderId;
    }
}

class Customer {
    private int id;

    public Customer(int customerId) {
        this.id = customerId;
    }
}

Vector API(第四次預覽)

新的 Vector API與java.util.Vector類沒有關係。事實上,它是關於數學向量計算的新 API 及其與現代SIMD(單指令-多數據)CPU的映射。

詳見:JDK Enhancement Proposal 426

棄用和刪除的一些 API

在 Java SE 19 中,一些函數被標記為 “廢棄 “或無法使用。

廢棄的 Locale 類構造函數

在 Java SE 19 中,Locale 類的公共構造函數被標記為 “棄用”。

相反,我們應該使用新的靜態工廠方法Locale.of()。這可以確保每個 Locale 配置只有一個實例。

下面的例子顯示了與構造函數相比工廠方法的使用情況。

示例代碼如下

package git.snippets.jdk19;

import java.util.Locale;

/**
 * @author <a href="mailto:[email protected]">Grey</a>
 * @date 2022/9/22
 * @since 19
 */
public class LocaleTest {
    public static void main(String[] args) {
        Locale german1 = new Locale("de"); // deprecated
        Locale germany1 = new Locale("de", "DE"); // deprecated

        Locale german2 = Locale.of("de");
        Locale germany2 = Locale.of("de", "DE");

        System.out.println("german1 == Locale.GERMAN = " + (german1 == Locale.GERMAN));
        System.out.println("germany1 == Locale.GERMANY = " + (germany1 == Locale.GERMANY));
        System.out.println("german2 == Locale.GERMAN = " + (german2 == Locale.GERMAN));
        System.out.println("germany2 == Locale.GERMANY = " + (germany2 == Locale.GERMANY));
    }
}

java.lang.ThreadGroup

在 Java SE 14 和 Java SE 16 中,有幾個 Thread 和 ThreadGroup 方法被標記為 “被廢棄”

以下這些方法在 Java 19 中已被停用。

ThreadGroup.destroy(); //- 該方法的調用將被忽略。
        ThreadGroup.isDestroyed() ;//- 總是返回false。
        ThreadGroup.setDaemon() ; //- 設置守護者標誌,但這已經沒有效果了。
        ThreadGroup.suspend();//會拋出一個UnsupportedOperationException。
        ThreadGroup.resume();//會拋出一個UnsupportedOperationException。
        ThreadGroup.stop();//會拋出一個UnsupportedOperationException。

所有關於 Java SE 19 的新特性見:JDK 19 Release Notes

更多

Java SE 7及以後各版本新增特性

參考資料

Java 19 Features (with Examples)

JDK 19 Release Notes