Java 18 新功能介紹

程式猿阿朗發表於2022-04-11

文章持續更新,可以關注公眾號程式猿阿朗或訪問未讀程式碼部落格
本文 Github.com/niumoo/JavaNotes 已經收錄,歡迎Star。
Java 18 在2022 年 3 月 22 日正式釋出,Java 18 不是一個長期支援版本,這次更新共帶來 9 個新功能。

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

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

JEP 描述
JEP 400 預設為 UTF-8
JEP 408 簡單的網路伺服器
JEP 413 Java API 文件中的程式碼片段
JEP 416 使用方法控制程式碼重新實現核心反射
JEP 417 Vector API(三次孵化)
JEP 418 網際網路地址解析 SPI
JEP 419 Foreign Function & Memory API (二次孵化)
JEP 420 switch 模式匹配(二次預覽)
JEP 421 棄用完成刪除

JEP 400:預設 UTF-8 字元編碼

JDK 一直都是支援 UTF-8 字元編碼,這次是把 UTF-8 設定為了預設編碼,也就是在不加任何指定的情況下,預設所有需要用到編碼的 JDK API 都使用 UTF-8 編碼,這樣就可以避免因為不同系統,不同地區,不同環境之間產生的編碼問題。

Mac OS 預設使用 UTF-8 作為預設編碼,但是其他作業系統上,編碼可能取決於系統的配置或者所在區域的設定。如中國大陸的 windows 使用 GBK 作為預設編碼。很多同學初學 Java 時可能都遇到過一個正常編寫 Java 類,在 windows 系統的命令控制檯中執行卻出現亂碼的情況。

使用下面的命令可以輸出 JDK 的當前編碼。

# Mac 系統,預設 UTF-8
➜  ~ java -XshowSettings:properties -version 2>&1 | grep file.encoding
    file.encoding = UTF-8
    file.encoding.pkg = sun.io
➜  ~

下面編寫一個簡單的 Java 程式,輸出預設字元編碼,然後輸出中文漢字 ”你好“,看看 Java 18 和 Java 17 執行區別。

系統環境:Windows 11

import java.nio.charset.Charset;

public class Hello{
    public static void main(String[] args) {
        System.out.println(Charset.defaultCharset());        System.out.println("你好");    }
}

從下面的執行結果中可以看到,使用 JDK 17 執行輸出的預設字元編碼是 GBK,輸出的中文 ”你好“ 已經亂碼了;亂碼是因為 VsCode 預設的文字編輯器編碼是 UTF-8,而中國地區的 Windows 11 預設字元編碼是 GBK,也是 JDK 17 預設獲取到的編碼,所以會在控制檯輸出時亂碼;而使用 JDK 18 輸出的預設編碼就是 UTF-8,所以可以正常的輸出中文 ”你好“。

JEP 408:簡單的 Web伺服器

在 Java 18 中,提供了一個新命令 jwebserver,執行這個命令可以啟動一個簡單的 、最小化的靜態Web 伺服器,它不支援 CGI 和 Servlet,所以最好的使用場景是用來測試、教育、演示等需求。

其實在如 Python、Ruby、PHP、Erlang 等許多平臺都提供了開箱即用的 Web 伺服器,可見一個簡單的Web 伺服器是一個常見的需求,Java 一直沒有這方面的支援,現在可以了。

在 Java 18 中,使用 jwebserver 啟動一個 Web 伺服器,預設釋出的是當前目錄。

在當前目錄建立一個網頁檔案 index.html

<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
</head>
<body>
<h1>標題</h1>
</body>
</html>

啟動 jwebserver.

➜  bin ./jwebserver
Binding to loopback by default. For all interfaces use "-b 0.0.0.0" or "-b ::".
Serving /Users/darcy/develop/jdk-18.jdk/Contents/Home/bin and subdirectories on 127.0.0.1 port 8000
URL http://127.0.0.1:8000/

瀏覽器訪問:

有請求時會在控制檯輸出請求資訊:

127.0.0.1 - - [26/3月/2022:16:53:30 +0800] "GET /favicon.ico HTTP/1.1" 404 -
127.0.0.1 - - [26/3月/2022:16:55:13 +0800] "GET / HTTP/1.1" 200 -

通過 help 引數可以檢視 jwebserver 支援的引數。

➜  bin ./jwebserver --help
Usage: jwebserver [-b bind address] [-p port] [-d directory]
                  [-o none|info|verbose] [-h to show options]
                  [-version to show version information]
Options:
-b, --bind-address    - 繫結地址. Default: 127.0.0.1 (loopback).
                        For all interfaces use "-b 0.0.0.0" or "-b ::".
-d, --directory       - 指定目錄. Default: current directory.
-o, --output          - Output format. none|info|verbose. Default: info.
-p, --port            - 繫結埠. Default: 8000.
-h, -?, --help        - Prints this help message and exits.
-version, --version   - Prints version information and exits.
To stop the server, press Ctrl + C.

JEP 413:Javadoc 中支援程式碼片段

在 Java 18 之前,已經支援在 Javadoc 中引入程式碼片段,這樣可以在某些場景下更好的展示描述資訊,但是之前的支援功能有限,比如我想高亮程式碼片段中的某一段程式碼是無能為力的。現在 Java 18 優化了這個問題,增加了 @snippet 來引入更高階的程式碼片段。

在 Java 18 之前,使用 <pre>{@code ...}</pre> 來引入程式碼片段。

 /**
  * 時間工具類
  * Java 18 之前引入程式碼片段:
  * <pre>{@code
  *     public static String timeStamp() {
  *        long time = System.currentTimeMillis();
  *         return String.valueOf(time / 1000);
  *     }
  * }</pre>
  *
  */

生成 Javadoc 之後,效果如下:

高亮程式碼片段

從 Java 18 開始,可以使用 @snippet 來生成註釋,且可以高亮某個程式碼片段。

/**
 * 在 Java 18 之後可以使用新的方式
 * 下面的程式碼演示如何使用 {@code Optional.isPresent}:
 * {@snippet :
 * if (v.isPresent()) {
 *     System.out.println("v: " + v.get());
 * }
 * }
 *
 * 高亮顯示 println
 *
 * {@snippet :
 * class HelloWorld {
 *     public static void main(String... args) {
 *         System.out.println("Hello World!");      // @highlight substring="println"
 *     }
 * }
 * }
 *
 */

效果如下,更直觀,效果更好。

正則高亮程式碼片段

甚至可以使用正則來高亮某一段中的某些關鍵詞:

/** 
  * 正則高亮:
  * {@snippet :
  *   public static void main(String... args) {
  *       for (var arg : args) {                 // @highlight region regex = "\barg\b"
  *           if (!arg.isBlank()) {
  *               System.out.println(arg);
  *           }
  *       }                                      // @end
  *   }
  *   }
  */

生成的 Javadoc 效果如下:

替換程式碼片段

可以使用正規表示式來替換某一段程式碼。

 /** 
   * 正則替換:
   * {@snippet :
   * class HelloWorld {
   *     public static void main(String... args) {
   *         System.out.println("Hello World!");  // @replace regex='".*"' replacement="..."
   *     }
   * }
   * }
   */

這段註釋會生成如下 Javadoc 效果。

class HelloWorld {
    public static void main(String... args) {
        System.out.println(...);
    }
}

附:Javadoc 生成方式

# 使用 javadoc 命令生成 Javadoc 文件
➜  bin ./javadoc -public -sourcepath ./src -subpackages com -encoding utf-8 -charset utf-8 -d ./javadocout
# 使用 Java 18 的 jwebserver 把生成的 Javadoc 釋出測試
➜  bin ./jwebserver -d /Users/darcy/develop/javadocout

訪問測試:

JEP 416:使用方法控制程式碼重新實現反射核心功能

Java 18 改進了 java.lang.reflect.MethodConstructor 的實現邏輯,使之效能更好,速度更快。這項改動不會改動相關 API ,這意味著開發中不需要改動反射相關程式碼,就可以體驗到效能更好反射。

OpenJDK 官方給出了新老實現的反射效能基準測試結果。

Java 18 之前:

Benchmark                                     Mode  Cnt   Score  Error  Units
ReflectionSpeedBenchmark.constructorConst     avgt   10  68.049 ± 0.872  ns/opReflectionSpeedBenchmark.constructorPoly      avgt   10  94.132 ± 1.805  ns/op
ReflectionSpeedBenchmark.constructorVar       avgt   10  64.543 ± 0.799  ns/op
ReflectionSpeedBenchmark.instanceFieldConst   avgt   10  35.361 ± 0.492  ns/opReflectionSpeedBenchmark.instanceFieldPoly    avgt   10  67.089 ± 3.288  ns/op
ReflectionSpeedBenchmark.instanceFieldVar     avgt   10  35.745 ± 0.554  ns/op
ReflectionSpeedBenchmark.instanceMethodConst  avgt   10  77.925 ± 2.026  ns/op
ReflectionSpeedBenchmark.instanceMethodPoly   avgt   10  96.094 ± 2.269  ns/op
ReflectionSpeedBenchmark.instanceMethodVar    avgt   10  80.002 ± 4.267  ns/op
ReflectionSpeedBenchmark.staticFieldConst     avgt   10  33.442 ± 2.659  ns/op
ReflectionSpeedBenchmark.staticFieldPoly      avgt   10  51.918 ± 1.522  ns/op
ReflectionSpeedBenchmark.staticFieldVar       avgt   10  33.967 ± 0.451  ns/op
ReflectionSpeedBenchmark.staticMethodConst    avgt   10  75.380 ± 1.660  ns/op
ReflectionSpeedBenchmark.staticMethodPoly     avgt   10  93.553 ± 1.037  ns/op
ReflectionSpeedBenchmark.staticMethodVar      avgt   10  76.728 ± 1.614  ns/op

Java 18 的新實現:

Benchmark                                     Mode  Cnt    Score   Error  Units
ReflectionSpeedBenchmark.constructorConst     avgt   10   32.392 ± 0.473  ns/opReflectionSpeedBenchmark.constructorPoly      avgt   10  113.947 ± 1.205  ns/op
ReflectionSpeedBenchmark.constructorVar       avgt   10   76.885 ± 1.128  ns/op
ReflectionSpeedBenchmark.instanceFieldConst   avgt   10   18.569 ± 0.161  ns/opReflectionSpeedBenchmark.instanceFieldPoly    avgt   10   98.671 ± 2.015  ns/op
ReflectionSpeedBenchmark.instanceFieldVar     avgt   10   54.193 ± 3.510  ns/op
ReflectionSpeedBenchmark.instanceMethodConst  avgt   10   33.421 ± 0.406  ns/op
ReflectionSpeedBenchmark.instanceMethodPoly   avgt   10  109.129 ± 1.959  ns/op
ReflectionSpeedBenchmark.instanceMethodVar    avgt   10   90.420 ± 2.187  ns/op
ReflectionSpeedBenchmark.staticFieldConst     avgt   10   19.080 ± 0.179  ns/op
ReflectionSpeedBenchmark.staticFieldPoly      avgt   10   92.130 ± 2.729  ns/op
ReflectionSpeedBenchmark.staticFieldVar       avgt   10   53.899 ± 1.051  ns/op
ReflectionSpeedBenchmark.staticMethodConst    avgt   10   35.907 ± 0.456  ns/op
ReflectionSpeedBenchmark.staticMethodPoly     avgt   10  102.895 ± 1.604  ns/op
ReflectionSpeedBenchmark.staticMethodVar      avgt   10   82.123 ± 0.629  ns/op

可以看到在某些場景下效能稍微好些。

JEP 417:Vector API(三次孵化)

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

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

現在在 JDK 18 中將繼續優化其效能。

JEP 418:網際網路地址解析 SPI

對於網際網路地址解析 SPI,為主機地址和域名地址解析定義一個 SPI,以便java.net.InetAddress可以使用平臺內建解析器以外的解析器。

InetAddress inetAddress = InetAddress.getByName("www.wdbyte.com");
System.out.println(inetAddress.getHostAddress());
// 輸出
// 106.14.229.49

JEP 419:Foreign Function & Memory API (第二次孵化)

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

這是一個孵化功能;需要新增--add-modules jdk.incubator.foreign來編譯和執行 Java 程式碼,Java 18 改進了相關 API ,使之更加簡單易用。

歷史

JEP 420:switch 表示式(二次孵化)

從 Java 17 開始,對於 Switch 的改進就已經在進行了,Java 17 的 JEP 406 已經對 Switch 表示式進行了增強,使之可以減少程式碼量。

下面是幾個例子:

// JDK 17 以前
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 之後,可以通過下面的寫法進行改進:

// JDK 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 421:棄用刪除相關

在未來將刪除 Finalization,目前 Finalization 仍預設保持啟用狀態,但是已經可以手動禁用;在未來的版本中,將會預設禁用;在以後的版本中,它將被刪除。需要進行資源管理可以嘗試 try-with-resources 或者 java.lang.ref.Cleaner

參考

訂閱

可以微信搜一搜程式猿阿朗或訪問未讀程式碼部落格閱讀。
本文 Github.com/niumoo/JavaNotes 已經收錄,歡迎Star。

相關文章