Java 16 新功能介紹

語言: CN / TW / HK

Java 16 在 2021 年 3 月 16 日正式釋出,不是長久支援版本,這次更新沒有帶來很多語法上的改動,但是也帶來了不少新的實用功能。

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

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

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

1. JEP 347: 啟用 C++ 14 語言特性

這項更新和 Java 開發者關係不太密切,JEP 347 允許 在 JDK 的 C++ 原始碼中使用 C++ 14 的語言特性,並且給出了哪些特性可以在 HotSpot 程式碼中使用的具體說明。

擴充套件閱讀: 啟用 C++ 14 語言特性

2. JEP 357:從 Mercurial 遷移到 Git

在此之前,OpenJDK 原始碼是使用版本管理工具 Mercurial 進行管理的,你也可以在 http://hg.openjdk.java.net/ 檢視 OpenJDK 的原始碼歷史版本。

但是現在遷移到了 GIt ,主要原因如下:

  1. Mercurial 生成的版本控制元資料過大。
  2. Mercurial 相關的開發工具比較少,而 Git 幾乎在所有的主流 IDE 中已經無縫整合。
  3. Mercurial 相關的服務比較少,無論是自建託管,還是服務託管。

為了優雅的遷移到 Git,OpenJDK 做了如下操作。

  1. 將所有的單儲存庫 OpenJDK 專案從 Mercurial 遷移到 Git。
  2. 保留所有的版本控制歷史,也包括 Tag。
  3. 根據 Git 的最佳實踐重新格式化提交的訊息。
  4. 建立了一個工具用來在 Mercurial 和 Git 雜湊之間進行轉換。

擴充套件閱讀: 從 Mercurial 遷移到 Git

3. JEP 369:遷移到 GitHub

和 JEP 357 從 Mercurial 遷移到 Git 的改變一致,在把版本管理遷移到 Git 之後,選擇了在 GitHub 上託管 OpenJDK 社群的 Git 倉庫。不過只對 JDK 11 以及更高版本 JDK 進行了遷移。

4. JEP 376:ZGC 併發執行緒堆疊處理

這次改動讓 ZGC 執行緒堆疊處理從 安全點(Safepoints) 移動到併發階段。

如果你忘記了什麼是 Safepoints,可以複習一下。

我們都知道,在之前,需要 GC 的時候,為了進行垃圾回收,需要所有的執行緒都暫停下來,這個暫停的時間我們成為 Stop The World

而為了實現 STW 這個操作, JVM 需要為每個執行緒選擇一個點停止執行,這個點就叫做 安全點(Safepoints)

擴充套件閱讀: JEP 376:ZGC 併發執行緒堆疊處理

5. JEP 380:Unix 域套接字通道

新增 UnixDomainSocketAddress.java 類用於支援 Unix 域套接字通道。

新增 Unix-domain socket 到 SocketChannel 和 ServerSocketChannel API 中。

新增列舉資訊 java.net.StandardProtocolFamily.UNIX。

6. JEP 386:移植 Alpine Linux

Apine Linux是一個獨立的、非商業的 Linux 發行版,它十分的小,一個容器需要不超過 8MB 的空間,最小安裝到磁碟只需要大約 130MB 儲存空間,並且十分的簡單,同時兼顧了安全性。

此提案將 JDK 移植到了 Apline Linux,由於 Apline Linux 是基於 musl lib 的輕量級 Linux 發行版,因此其他 x64 和 AArch64 架構上使用 musl lib 的 Linux 發行版也適用。

擴充套件閱讀: JEP 386: Alpine Linux Port

7. JEP 387:更好的 Metaspace

自從引入了 Metaspace 以來,根據反饋,Metaspace 經常佔用過多的堆外記憶體,從而導致記憶體浪費,現在可以更及時地將未使用的 HotSpot class-metaspace 記憶體 返還 給作業系統,從而減少 Metaspace 的佔用空間,並優化了 Metaspace 程式碼以降低後續的維護成本。

8. JEP 388:移植 Windows/AArch64

將 JDK 移植到 Windows/AArch64 架構上,Windows/AArch64 已經是終端使用者市場的熱門需求。

9. JEP 389:外部聯結器 API(孵化)

這項提案讓 Java 程式碼可以呼叫由其他語言(比如 C ,C++)編寫的編譯後的機器程式碼,替換了之前的 JNI 形式。

不過這還是一個孵化中的功能,執行時需要新增 --add-modules jdk.incubator.foreign 引數來編譯和執行 Java 程式碼。

下面是一個呼叫 C 語言函式方法,然後輸出執行結果的例子。

  1. 編寫一個 C 函式列印一個 “hellowww.wdbyte.com"。
#include <stdio.h>

void printHello(){
	printf("hello www.wdbyte.com\n");
}
  1. 將上面的程式碼編譯,然後輸出到共享庫 hello.so
$ gcc -c -fPIC hello.c
$ gcc -shared -o hello.so hello.o
$ ll
total 128
-rw-r--r--  1 darcy  staff    76B 10 28 19:46 hello.c
-rw-r--r--  1 darcy  staff   776B 10 28 19:46 hello.o
-rwxr-xr-x  1 darcy  staff    48K 10 28 19:47 hello.so
  1. 編寫一個 Java 程式碼,呼叫 hello.so 的 printHello 方法。
import jdk.incubator.foreign.CLinker;
import jdk.incubator.foreign.FunctionDescriptor;
import jdk.incubator.foreign.LibraryLookup;

import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodType;
import java.nio.file.Path;
import java.util.Optional;

public class JEP389 {

  public static void main(String[] args) throws Throwable {

      Path path = Path.of("/Users/darcy/git/java-core/java-16/src/com/wdbyte/hello.so");

      LibraryLookup libraryLookup = LibraryLookup.ofPath(path);

      Optional<LibraryLookup.Symbol> optionalSymbol = libraryLookup.lookup("printHello");
      if (optionalSymbol.isPresent()) {

          LibraryLookup.Symbol symbol = optionalSymbol.get();

          FunctionDescriptor functionDescriptor = FunctionDescriptor.ofVoid();

          MethodType methodType = MethodType.methodType(Void.TYPE);

          MethodHandle methodHandle = CLinker.getInstance().downcallHandle(
                  symbol.address(),
                  methodType,
                  functionDescriptor);
          methodHandle.invokeExact();

      }

  }
}
  1. Java 程式碼編譯。
$ javac --add-modules jdk.incubator.foreign JEP389.java
警告: 使用 incubating 模組: jdk.incubator.foreign
1 個警告
  1. Java 程式碼執行。
$ java --add-modules  jdk.incubator.foreign -Dforeign.restricted=permit JEP389.java
WARNING: Using incubator modules: jdk.incubator.foreign
警告: 使用 incubating 模組: jdk.incubator.foreign
1 個警告
hello www.wdbyte.com

擴充套件閱讀: JEP 389:外部連結器 API(孵化器)

10. JEP 390:基於值的類的警告

添加了一個註解,用於標識當前是是基於值的類,比如 Java 8 引入的預防空指標的 Optional 類,現在已經添加了註解標識。

@jdk.internal.ValueBased
public final class Optional<T> {
    // ...
}

擴充套件閱讀: 基於值的類的警告

11. JEP 392:打包工具

在 Java 14 中,JEP 343 引入了打包工具,命令是 jpackage ,在 Java 14 新功能文章裡也做了介紹:

使用 jpackage 命令可以把 JAR 包打包成不同作業系統支援的軟體格式。

>jpackage --name myapp --input lib --main-jar main.jar --main-class myapp.Main

常見平臺格式如下:

  1. Linux: deb and rpm
  2. macOS: pkg and dmg
  3. Windows: msi and exe

要注意的是, jpackage 不支援交叉編譯,也就是說在 windows 平臺上是不能打包成 macOS 或者 Linux 系統的軟體格式的。

在 Java 15 中,繼續孵化,現在在 Java 16 中,終於成為了正式功能。

下面是一個例子,把一個簡單的 Java Swing 程式打包成當前作業系統支援的軟體格式,然後安裝到當前電腦。

編寫 Java 程式碼

import javax.swing.*;
import java.awt.*;

public class JEP392 {

    public static void main(String[] args) {
        JFrame frame = new JFrame("Hello World Java Swing");
        frame.setMinimumSize(new Dimension(800, 600));
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        JLabel lblText = new JLabel("Hello World!", SwingConstants.CENTER);
        frame.getContentPane().add(lblText);
        frame.pack();
        frame.setVisible(true);

    }
}

編譯後,建立一個 JAR 檔案。

$ javac JEP392.java
$ java JEP392.java
$ jar cvf JEP392.jar JEP392.class

將生成的 JEP392.jar 打包到符合當前平臺的軟體包中。

$ ~/develop/jdk-16.0.1.jdk/Contents/Home/bin/jpackage -i . -n JEP392 --main-jar hello.jar --main-class JEP392
$ ll
[email protected] 1 darcy  staff    50M 10 28 20:34 JEP392-1.0.dmg
-rw-r--r--  1 darcy  staff   864B 10 28 20:22 JEP392.class
-rw-r--r--  1 darcy  staff   1.0K 10 28 20:30 JEP392.jar
-rw-r--r--  1 darcy  staff   588B 10 28 20:22 JEP392.java

ll 後顯示的 JEP392-1.0.dmg (我用的 MacOS ,所以格式是 dmg)就是打包後的結果。

雙擊這個檔案後可以像 mac 軟體一樣安裝。其他平臺類似。

安裝後可以在啟動臺啟動。

不同的系統安裝位置不同:

/opt
/Applications
C:\Program Files\

擴充套件閱讀: JEP 392:打包工具

12. JEP 393:外部記憶體訪問(第三次孵化)

此提案旨在引入新的 API 以允許 Java 程式安全有效的訪問 Java 堆之外的記憶體。相關提案早在 Java 14 的時候就已經提出了,在 Java 15 中重新孵化,現在在 Java 16 中再次孵化。

此提案的目標如下:

sun.misc.Unsafa

擴充套件閱讀: 外部記憶體訪問

13. JEP 394:instanceof 模式匹配

改進 instanceof 在 Java 14 中已經提出,在 Java 15 中繼續預覽,而現在,在 Java 16 中成為正式功能。

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

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

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

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

擴充套件閱讀: Java 14 新特性介紹 - instanceof

14. JEP 395:Records

Record成為 Java 16 的正式功能,下面是介紹 Java 14 時關於 Record 的介紹。

record 是一種全新的型別,它本質上是一個 final 類,同時所有的屬性都是 final 修飾,它會自動編譯出 public get hashcodeequalstoString 等方法,減少了程式碼編寫量。

示例:編寫一個 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 15 中進行二次預覽,在 Java 16 中正式釋出。

15. JEP 396:預設強封裝JDK內部

Java 9 JEP 261 引入了 --illegal-access 控制內部 API 訪問和 JDK 打包的選項。

此 JEP 將 --illegal-access 選項的預設模式從允許更改為拒絕。通過此更改,JDK的內部包和 API( 關鍵內部 API 除外)將不再預設開啟。

該 JEP 的動機是阻止第三方庫、框架和工具使用 JDK 的內部 API 和包,增加了安全性。

16. JEP 397:Sealed Classes(密封類)預覽

Sealed Classes 再次預覽,在 Java 15 新特性介紹文章裡已經介紹過相關功能,並且給出了詳細的使用演示,這裡不再重複介紹。

下面是一段引用:

我們都知道,在 Java 中如果想讓一個類不能被繼承和修改,這時我們應該使用 final 關鍵字對類進行修飾。不過這種要麼可以繼承,要麼不能繼承的機制不夠靈活,有些時候我們可能想讓某個類可以被某些型別繼承,但是又不能隨意繼承,是做不到的。Java 15 嘗試解決這個問題,引入了 sealed 類,被 sealed 修飾的類可以指定子類。這樣這個類就只能被指定的類繼承。

而且 sealed 修飾的類的機制具有傳遞性,它的子類必須使用指定的關鍵字進行修飾,且只能是 finalsealednon-sealed 三者之一。

擴充套件閱讀:Java 15 新特性介紹

參考

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