Java 17 新功能介紹(LTS)

程式猿阿朗發表於2021-11-08

點贊再看,動力無限。Hello world : ) 微信搜「 程式猿阿朗 」。

本文 Github.com/niumoo/JavaNotes未讀程式碼部落格 已經收錄,有很多知識點和系列文章。

Java 17 在 2021 年 9 月 14 日正式釋出,Java 17 是一個長期支援(LTS)版本,這次更新共帶來 14 個新功能。

OpenJDK Java 17 下載:https://jdk.java.net/archive/

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

JEP 描述
JEP 306 恢復始終嚴格的浮點語義
JEP 356 增強的偽隨機數生成器
JEP 382 使用新的 macOS 渲染庫
JEP 391 支援 macOS/AArch64 架構
JEP 398 刪除已啟用的 Applet API
JEP 403 更強的封裝 JDK 內部封裝
JEP 406 Switch 模式匹配(預覽)
JEP 407 移除 RMI Activation
JEP 409 密封類(Sealed Classes)
JEP 410 JEP 401:移除實驗性的 AOT 和 JIT 編譯器
JEP 411 棄用 Security Manager
JEP 412 外部函式和記憶體 API(孵化器)
JEP 414 Vector API(第二孵化器)
JEP 415 指定上下文的反序列化過濾器

此文章屬於 Java 新特性教程 系列,會介紹 Java 每個版本的新功能,可以點選瀏覽。

1. JEP 306: 恢復始終嚴格的浮點語義

既然是恢復嚴格的浮點語義,那麼說明在某個時間點之前,是始終嚴格的浮點語義的。其實在 Java SE 1.2 之前,所有的浮點計算都是嚴格的,但是以當初的情況來看,過於嚴格的浮點計算在當初流行的 x86 架構和 x87 浮點協議處理器上執行,需要大量的額外的指令開銷,所以在 Java SE 1.2 開始,需要手動使用關鍵字 strictfp(strict float point) 才能啟用嚴格的浮點計算。

但是在 2021 年的今天,硬體早已發生鉅變,當初的問題已經不存在了,所以從 Java 17 開始,恢復了始終嚴格的浮點語義這一特性。

擴充套件strictfp 是 Java 中的一個關鍵字,大多數人可能沒有注意過它,它可以用在類、介面或者方法上,被 strictfp 修飾的部分中的 float 和 double 表示式會進行嚴格浮點計算。

下面是一個示例,其中的 testStrictfp()strictfp 修飾。

package com.wdbyte;

public class Main {
    public static void main(String[] args) {
        testStrictfp();
    }

    public strictfp static void testStrictfp() {
        float aFloat = 0.6666666666666666666f;
        double aDouble = 0.88888888888888888d;
        double sum = aFloat + aDouble;
        System.out.println("sum: " + sum);
    }
}

2. JEP 356:增強的偽隨機數生成器

為偽隨機數生成器 RPNG(pseudorandom number generator)增加了新的介面型別和實現,讓在程式碼中使用各種 PRNG 演算法變得容易許多。

這次增加了 RandomGenerator 介面,為所有的 PRNG 演算法提供統一的 API,並且可以獲取不同型別的 PRNG 物件流。同時也提供了一個新類 RandomGeneratorFactory 用於構造各種 RandomGenerator 例項,在 RandomGeneratorFactory 中使用 ServiceLoader.provider 來載入各種 PRNG 實現。

下面是一個使用示例:隨便選擇一個 PRNG 演算法生成 5 個 10 以內的隨機數。

package com.wdbyte.java17;

import java.util.Date;
import java.util.random.RandomGenerator;
import java.util.random.RandomGeneratorFactory;
import java.util.stream.Stream;

/**
 * @author niulang
 */
public class JEP356 {

    public static void main(String[] args) {
        RandomGeneratorFactory<RandomGenerator> l128X256MixRandom = RandomGeneratorFactory.of("L128X256MixRandom");
        // 使用時間戳作為隨機數種子
        RandomGenerator randomGenerator = l128X256MixRandom.create(System.currentTimeMillis());
        for (int i = 0; i < 5; i++) {
            System.out.println(randomGenerator.nextInt(10));
        }
    }
}

得到輸出:

7
3
4
4
6

你也可以遍歷出所有的 PRNG 演算法。

RandomGeneratorFactory.all().forEach(factory -> {
    System.out.println(factory.group() + ":" + factory.name());
});

得到輸出:

LXM:L32X64MixRandom
LXM:L128X128MixRandom
LXM:L64X128MixRandom
Legacy:SecureRandom
LXM:L128X1024MixRandom
LXM:L64X128StarStarRandom
Xoshiro:Xoshiro256PlusPlus
LXM:L64X256MixRandom
Legacy:Random
Xoroshiro:Xoroshiro128PlusPlus
LXM:L128X256MixRandom
Legacy:SplittableRandom
LXM:L64X1024MixRandom

可以看到 Legacy:Random 也在其中,新的 API 相容了老的 Random 方式,所以你也可以使用新的 API 呼叫 Random 類生成隨機數。

// 使用 Random
RandomGeneratorFactory<RandomGenerator> l128X256MixRandom = RandomGeneratorFactory.of("Random");
// 使用時間戳作為隨機數種子
RandomGenerator randomGenerator = l128X256MixRandom.create(System.currentTimeMillis());
for (int i = 0; i < 5; i++) {
    System.out.println(randomGenerator.nextInt(10));
}

擴充套件閱讀:增強的偽隨機數生成器

3. JEP 382:使用新的 macOS 渲染庫

macOS 為了提高圖形的渲染效能,在 2018 年 9 月拋棄了之前的 OpenGL 渲染庫 ,而使用了 Apple Metal 進行代替。Java 17 這次更新開始支援 Apple Metal,不過對於 API 沒有任何改變,這一些都是內部修改。

擴充套件閱讀:macOS Mojave 10.14 Release NotesApple Metal

4. JEP 391:支援 macOS/AArch64 架構

起因是 Apple 在 2020 年 6 月的 WWDC 演講中宣佈,將開啟一項長期的將 Macintosh 計算機系列從 x64 過度到 AArch64 的長期計劃,因此需要儘快的讓 JDK 支援 macOS/AArch64 。

Linux 上的 AArch64 支援以及在 Java 16 時已經支援,可以檢視之前的文章瞭解。

擴充套件:Java 16 新功能介紹 - JEP 386

5. JEP 398:刪除已棄用的 Applet API

Applet 是使用 Java 編寫的可以嵌入到 HTML 中的小應用程式,嵌入方式是通過普通的 HTML 標記語法,由於早已過時,幾乎沒有場景在使用了。

示例:嵌入 Hello.class

<applet code="Hello.class" height=200 width=200></applet>

Applet API 在 Java 9 時已經標記了廢棄,現在 Java 17 中將徹底刪除。

6. JEP 403:更強的 JDK 內部封裝

如 Java 16 的 JEP 396 中描述的一樣,為了提高 JDK 的安全性,使 --illegal-access 選項的預設模式從允許更改為拒絕。通過此更改,JDK 的內部包和 API(關鍵內部 API 除外)將不再預設開啟。

但是在 Java 17 中,除了 sun.misc.Unsafe ,使用 --illegal-access 命令也不能開啟 JDK 內部的強封裝模式了,除了 sun.misc.Unsafe API .

在 Java 17 中使用 --illegal-access 選項將會得到一個命令已經移除的警告。

➜  bin ./java -version
openjdk version "17" 2021-09-14
OpenJDK Runtime Environment (build 17+35-2724)
OpenJDK 64-Bit Server VM (build 17+35-2724, mixed mode, sharing)
➜  bin ./java --illegal-access=warn
OpenJDK 64-Bit Server VM warning: Ignoring option --illegal-access=warn; support was removed in 17.0

擴充套件閱讀:JEP 403:更強的 JDK 內部封裝Java 16 新功能介紹

7. JEP 406:switch 的型別匹配(預覽)

instanceof 一樣,為 switch 也增加了型別匹配自動轉換功能。

在之前,使用 instanceof 需要如下操作:

if (obj instanceof String) {
    String s = (String) obj;    // grr...
    ...
}

多餘的型別強制轉換,而現在:

if (obj instanceof String s) {
    // Let pattern matching do the work!
    ...
}

switch 也可以使用類似的方式了。

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

對於 null 值的判斷也有了新的方式。

// Java 17 之前
static void testFooBar(String s) {
    if (s == null) {
        System.out.println("oops!");
        return;
    }
    switch (s) {
        case "Foo", "Bar" -> System.out.println("Great");
        default           -> System.out.println("Ok");
    }
}
// Java 17
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");
    }
}

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

8. JEP 407:移除 RMI Activation

移除了在 JEP 385 中被標記廢除的 RMI(Remote Method Invocation)Activation,但是 RMI 其他部分不會受影響。

RMI Activation 在 Java 15 中的 JEP 385 已經被標記為過時廢棄,至今沒有收到不良反饋,因此決定在 Java 17 中正式移除。

擴充套件閱讀: JEP 407:移除 RMI Activation

9. JEP 409:密封類(Sealed Classes)

Sealed Classes 在 Java 15 中的 JEP 360 中提出,在 Java 16 中的 JEP 397 再次預覽,現在 Java 17 中成為正式的功能,相比 Java 16 並沒有功能變化,這裡不再重複介紹,想了解的可以參考之前文章。

擴充套件閱讀:Java 16 新功能介紹JEP 409: Sealed Classes

10. JEP 401:移除實驗性的 AOT 和 JIT 編譯器

在 Java 9 的 JEP 295 中,引入了實驗性的提前編譯 jaotc 工具,但是這個特性自從引入依賴用處都不太大,而且需要大量的維護工作,所以在 Java 17 中決定刪除這個特性。

主要移除了三個 JDK 模組:

  1. jdk.aot - jaotc 工具。
  2. Jdk.internal.vm.compiler - Graal 編譯器。
  3. jdk.internal.vm.compiler.management

同時也移除了部分與 AOT 編譯相關的 HotSpot 程式碼:

  1. src/hotspot/share/aot — dumps and loads AOT code
  2. Additional code guarded by #if INCLUDE_AOT

11. JEP 411:棄用 Security Manager

Security Manager 在 JDK 1.0 時就已經引入,但是它一直都不是保護服務端以及客戶端 Java 程式碼的主要手段,為了 Java 的繼續發展,決定棄用 Security Manager,在不久的未來進行刪除。

@Deprecated(since="17", forRemoval=true)
public class SecurityManager {
	// ...
}

12. JEP 412:外部函式和記憶體 API (孵化)

新的 API 允許 Java 開發者與 JVM 之外的程式碼和資料進行互動,通過呼叫外部函式,可以在不使用 JNI 的情況下呼叫本地庫。

這是一個孵化功能;需要新增--add-modules jdk.incubator.foreign來編譯和執行 Java 程式碼。

歷史

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

擴充套件閱讀:JEP 412:外部函式和記憶體 API (孵化)

13. JEP 414:Vector API(二次孵化)

在 Java 16 中引入一個新的 API 來進行向量計算,它可以在執行時可靠的編譯為支援的 CPU 架構,從而實現更優的計算能力。

現在 Java 17 中改進了 Vector API 效能,增強了例如對字元的操作、位元組向量與布林陣列之間的相互轉換等功能。

14. JEP 415:指定上下文的反序列化過濾器

Java 中的序列化一直都是非常重要的功能,如果沒有序列化功能,Java 可能都不會佔據開發語言的主導地位,序列化讓遠端處理變得容易和透明,同時也促進了 Java EE 的成功。

但是 Java 序列化的問題也很多,它幾乎會犯下所有的可以想象的錯誤,為開發者帶來持續的維護工作。但是要說明的是序列化的概念是沒有錯的,把物件轉換為可以在 JVM 之間自由傳輸,並且可以在另一端重新構建的能力是完全合理的想法,問題在於 Java 中的序列化設計存在風險,以至於爆出過很多和序列化相關的漏洞。

反序列化危險的一個原因是,有時候我們不好驗證將要進行反序列化的內容是否存在風險,而傳入的資料流可以自由引用物件,很有可能這個資料流就是攻擊者精心構造的惡意程式碼。

所以,JEP 415 允許在反序列化時,通過一個過濾配置,來告知本次反序列化允許或者禁止操作的類,反序列化時碰到被禁止的類,則會反序列化失敗。

14.1. 反序列化示例

假設 Dog 類中的 Poc 是惡意構造的類,但是正常反序列化是可以成功的。

package com.wdbyte.java17;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;

/**
 * @author niulang
 */
public class JEP415 {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        Dog dog = new Dog("哈士奇");
        dog.setPoc(new Poc());
        // 序列化 - 物件轉位元組陣列
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        try (ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);) {
            objectOutputStream.writeObject(dog);
        }
        byte[] bytes = byteArrayOutputStream.toByteArray();
        // 反序列化 - 位元組陣列轉物件
        ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes);
        ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream);
        Object object = objectInputStream.readObject();
        System.out.println(object.toString());
    }
}

class Dog implements Serializable {
    private String name;
    private Poc poc;

    public Dog(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "Dog{" + "name='" + name + '\'' + '}';
    }
		// get...set...
}

class Poc implements Serializable{

}

輸出結果:

Dog{name='哈士奇'}

14.2. 反序列化過濾器

在 Java 17 中可以自定義反序列化過濾器,攔截不允許的類。

package com.wdbyte.java17;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputFilter;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;

/**
 * @author niulang
 */
public class JEP415 {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        Dog dog = new Dog("哈士奇");
        dog.setPoc(new Poc());
        // 序列化 - 物件轉位元組陣列
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        try (ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);) {
            objectOutputStream.writeObject(dog);
        }
        byte[] bytes = byteArrayOutputStream.toByteArray();
        // 反序列化 - 位元組陣列轉物件
        ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes);
        ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream);
        // 允許 com.wdbyte.java17.Dog 類,允許 java.base 中的所有類,拒絕其他任何類
        ObjectInputFilter filter = ObjectInputFilter.Config.createFilter(
                        "com.wdbyte.java17.Dog;java.base/*;!*");
        objectInputStream.setObjectInputFilter(filter);
        Object object = objectInputStream.readObject();
        System.out.println(object.toString());
    }
}

class Dog implements Serializable {
    private String name;
    private Poc poc;

    public Dog(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "Dog{" + "name='" + name + '\'' + '}';
    }
		// get...set...
}

class Poc implements Serializable{
}

這時反序列化會得到異常。

Exception in thread "main" java.io.InvalidClassException: filter status: REJECTED
	at java.base/java.io.ObjectInputStream.filterCheck(ObjectInputStream.java:1412)
	at java.base/java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:2053)
	at java.base/java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1907)
	....

擴充套件閱讀:JEP 415:指定上下文的反序列化過濾器

參考

  1. https://openjdk.java.net/projects/jdk/17/
  2. https://docs.oracle.com/en/java/javase/17/

<完>

文章持續更新,可以微信搜一搜「 程式猿阿朗 」或訪問「程式猿阿朗部落格 」第一時間閱讀。本文 Github.com/niumoo/JavaNotes 已經收錄,有很多知識點和系列文章,歡迎Star。

Java 17 新功能介紹(LTS)

相關文章