我們一起聊聊Java 18 新功能,你知道了嗎?
Java 18 在2022 年 3 月 22 日正式發(fā)布,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[1] |
JEP 408 | 簡單的網絡服務器[2] |
JEP 413 | Java API 文檔中的代碼片段[3] |
JEP 416 | 使用方法句柄重新實現核心反射[4] |
JEP 417 | Vector API(三次孵化)[5] |
JEP 418 | 互聯網地址解析 SPI[6] |
JEP 419 | Foreign Function & Memory API (二次孵化)[7] |
JEP 420 | switch 模式匹配(二次預覽)[8] |
JEP 421 | 棄用完成刪除[9] |
JEP 400:默認 UTF-8 字符編碼
JDK 一直都是支持 UTF-8 字符編碼,這次是把 UTF-8 設置為了默認編碼,也就是在不加任何指定的情況下,默認所有需要用到編碼的 JDK API 都使用 UTF-8 編碼,這樣就可以避免因為不同系統(tǒng),不同地區(qū),不同環(huán)境之間產生的編碼問題。
Mac OS 默認使用 UTF-8 作為默認編碼,但是其他操作系統(tǒng)上,編碼可能取決于系統(tǒng)的配置或者所在區(qū)域的設置。如中國大陸的 windows 使用 GBK 作為默認編碼。很多同學初學 Java 時可能都遇到過一個正常編寫 Java 類,在 windows 系統(tǒng)的命令控制臺中運行卻出現亂碼的情況。
使用下面的命令可以輸出 JDK 的當前編碼。
# Mac 系統(tǒng),默認 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 運行區(qū)別。
系統(tǒng)環(huán)境: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,而中國地區(qū)的 Windows 11 默認字符編碼是 GBK,也是 JDK 17 默認獲取到的編碼,所以會在控制臺輸出時亂碼;而使用 JDK 18 輸出的默認編碼就是 UTF-8,所以可以正常的輸出中文 ”你好“。
JEP 408:簡單的 Web服務器
在 Java 18 中,提供了一個新命令 jwebserver,運行這個命令可以啟動一個簡單的 、最小化的靜態(tài)Web 服務器,它不支持 CGI 和 Servlet,所以最好的使用場景是用來測試、教育、演示等需求。
其實在如 Python、Ruby、PHP、Erlang 等許多平臺都提供了開箱即用的 Web 服務器,可見一個簡單的Web 服務器是一個常見的需求,Java 一直沒有這方面的支持,現在可以了。
在 Java 18 中,使用 jwebserver 啟動一個 Web 服務器,默認發(fā)布的是當前目錄。
在當前目錄創(chuàng)建一個網頁文件 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 優(yōu)化了這個問題,增加了 @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 之后,效果如下:
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"
* }
* }
* }
*
*/
效果如下,更直觀,效果更好。
Java 18 Javadoc
正則高亮代碼片段
甚至可以使用正則來高亮某一段中的某些關鍵詞:
/**
* 正則高亮:
* {@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 發(fā)布測試
? bin ./jwebserver -d /Users/darcy/develop/javadocout
訪問測試:
JEP 416:使用方法句柄重新實現反射核心功能
Java 18 改進了 java.lang.reflect.Method、Constructor 的實現邏輯,使之性能更好,速度更快。這項改動不會改動相關 API ,這意味著開發(fā)中不需要改動反射相關代碼,就可以體驗到性能更好反射。
OpenJDK 官方給出了新老實現的反射性能基準測試結果。
Java 18 之前:
Benchmark Mode Cnt Score Error Units
ReflectionSpeedBenchmark.constructorConst avgt 10 68.049 ± 0.872 ns/op
ReflectionSpeedBenchmark.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/op
ReflectionSpeedBenchmark.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/op
ReflectionSpeedBenchmark.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/op
ReflectionSpeedBenchmark.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 架構,從而實現更優(yōu)的計算能力。
在 Java 17 中改進了 Vector API 性能,增強了例如對字符的操作、字節(jié)向量與布爾數組之間的相互轉換等功能。
現在在 JDK 18 中將繼續(xù)優(yōu)化其性能。
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 開發(fā)者與 JVM 之外的代碼和數據進行交互,通過調用外部函數,可以在不使用 JNI 的情況下調用本地庫。
這是一個孵化功能;需要添加--add-modules jdk.incubator.foreign來編譯和運行 Java 代碼,Java 18 改進了相關 API ,使之更加簡單易用。
歷史
- Java 14 JEP 370 (opens new window)[10]引入了外部內存訪問 API(孵化器)。
- Java 15 JEP 383 (opens new window)[11]引入了外部內存訪問 API(第二孵化器)。
- Java 16 JEP 389 (opens new window)[12]引入了外部鏈接器 API(孵化器)。
- Java 16 JEP 393 (opens new window)[13]引入了外部內存訪問 API(第三孵化器)。
- Java 17 JEP 412 (opens new window)[14]引入了外部函數和內存 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 的類型匹配(預覽)[15]
JEP 421:棄用刪除相關
在未來將刪除 Finalization,目前 Finalization 仍默認保持啟用狀態(tài),但是已經可以手動禁用;在未來的版本中,將會默認禁用;在以后的版本中,它將被刪除。需要進行資源管理可以嘗試 try-with-resources 或者 java.lang.ref.Cleaner。
引用鏈接
[1] 默認為 UTF-8: https://openjdk.java.net/jeps/400
[2] 簡單的網絡服務器: https://openjdk.java.net/jeps/408
[3] Java API 文檔中的代碼片段: https://openjdk.java.net/jeps/413
[4] 使用方法句柄重新實現核心反射: https://openjdk.java.net/jeps/416
[5] Vector API(三次孵化): https://openjdk.java.net/jeps/417
[6] 互聯網地址解析 SPI: https://openjdk.java.net/jeps/418
[7] Foreign Function & Memory API (二次孵化): https://openjdk.java.net/jeps/419
[8] switch 模式匹配(二次預覽): https://openjdk.java.net/jeps/420
[9] 棄用完成刪除: https://openjdk.java.net/jeps/421
[10] JEP 370 (opens new window): https://openjdk.java.net/jeps/370
[11] JEP 383 (opens new window): https://openjdk.java.net/jeps/383
[12] JEP 389 (opens new window): https://openjdk.java.net/jeps/389
[13] JEP 393 (opens new window): https://openjdk.java.net/jeps/393
[14] JEP 412 (opens new window): https://openjdk.java.net/jeps/412