如今 Java 23 已完成功能特性開發(在撰寫本文時處于第二階段降速期),是時候來了解一下這個新版本為我們開發者帶來的所有功能了。
首先,發生了一件據我所知在 Java 版本發布中從未發生過的事情:一個預覽功能被移除了!在 Java 21 中作為預覽功能出現的字符串模板功能已被移除。該功能將進行全面重新設計,因為它引發了很多爭議,似乎也沒有達到社區的期望。
定義預覽功能流程的 JEP 12 明確指出,預覽功能可以由功能所有者自行決定移除,無需新的 JEP。
最終,JEP 所有者必須決定預覽功能的命運。如果決定移除預覽功能,那么所有者必須在 JBS 中提交一個問題,以便在下次 JDK 功能發布中移除該功能;無需新的 JEP。
這里就是這么做的。
JEP 455—— 模式、instanceof 和 switch 中的基本類型(預覽)
這一預覽功能為 instanceof 和 switch 增加了對基本類型的支持,并增強了模式匹配以支持基本類型模式:在 instanceof 中、在 switch 語句中以及在記錄解構中。
現在,switch 支持所有基本類型。
示例:
long l =...;
switch (l) {
case 1L ->...;
case 2L ->...;
case 10_000_000_000L ->...;
default ->...;
}
現在我們可以對所有基本類型使用 instanceof。
來自 JEP 的示例:
if (i instanceof byte) { // i 的值適合存儲在一個字節中
... (byte)i... // 需要傳統的強制類型轉換
}
但最有趣的是對模式匹配的支持。以下是一些現在可以在何處對基本類型使用模式匹配的示例。
來自 JEP 的在 switch 中對基本類型進行模式匹配的示例:
switch (x.getStatus()) {
case 0 -> "okay";
case 1 -> "warning";
case 2 -> "error";
case int i -> "unknown status: " + i;
}
還可以通過 when 子句支持守衛:
switch (x.getStatus()) {
case 0 -> "okay";
case 1 -> "warning";
case int i when i > 1 && i < 100 -> "client error: " + i;
case int i when i > 100 -> "server error: " + i;
}
來自 JEP 的在 instanceof 中對基本類型進行模式匹配的示例:
if (i instanceof byte b) {
... b...
}
來自 JEP 的在解構記錄時對基本類型進行模式匹配的示例:
// JSON 沒有區分整數和雙精度數,所以 JSON 數字應該是雙精度數。
record JsonNumber(double number) implements JsonValue { }
var number = new JsonNumber(30);
// 以前我們只能通過其確切的組件類型(long)來解構這個記錄,
// 現在我們可以解構并匹配不同的基本類型
if (json instanceof JsonObject(int number)) {
//...
}
這種演變需要在模式匹配中實現轉換規則,以便一個基本類型與另一個基本類型匹配,如在前面的示例中,30 匹配了一個 int,即使記錄組件被定義為 double,目標類型必須被模式測試覆蓋。在這里,30 被一個 int 覆蓋。未被覆蓋的值將被拒絕。
JEP 467——Markdown 文檔注釋
該功能允許你使用 Markdown 而不僅僅是 HTML 和 JavaDoc 標簽的混合來編寫 JavaDoc 文檔注釋。
編寫 HTML 代碼并不總是容易的,而且在沒有渲染的情況下可讀性也不高,JavaDoc 標簽有時使用起來很復雜。Markdown 是一種在沒有渲染的情況下也具有可讀性且易于使用的語言。將其用于 JavaDoc 注釋是一個很好的選擇。Markdown 支持使用 HTML 標簽,提供了極大的靈活性,同時如果需要,仍然支持特定于 JavaDoc 的標簽。
Markdown 注釋以三個斜杠開頭:///。
以下是來自 JEP 的一個示例:
/**
* 返回對象的哈希碼值。此方法是為了哈希表(如由{@link java.util.HashMap}提供的那些)的利益而支持的。
* <p>
* {@code hashCode}的一般約定是:
* <ul>
* <li>在 Java 應用程序的一次執行過程中,每當對同一對象多次調用它時,只要在對象的{@code equals}比較中使用的信息沒有被修改,{@code hashCode}方法就必須始終返回相同的整數。這個整數在同一應用程序的不同執行之間不需要保持一致。</li>
* <li>如果根據{@link #equals(Object)}方法兩個對象相等,那么對這兩個對象中的每一個調用{@code hashCode}方法必須產生相同的整數結果。</li>
* <li>如果根據{@link #equals(Object)}方法兩個對象不相等,那么對這兩個對象中的每一個調用{@code hashCode}方法并不要求必須產生不同的整數結果。然而,程序員應該意識到,為不相等的對象產生不同的整數結果可能會提高哈希表的性能。</li>
* </ul>
*
* @implSpec
* 在合理可行的范圍內,由類{@code Object}定義的{@code hashCode}方法為不同的對象返回不同的整數。
*
* @return 這個對象的哈希碼值。
* @see java.lang.Object#equals(java.lang.Object)
* @see java.lang.System#identityHashCode
*/
用 Markdown 可以這樣寫:
/// 返回對象的哈希碼值。此方法是為了哈希表(如由[java.util.HashMap]提供的那些)的利益而支持的。
///
/// `hashCode`的一般約定是:
///
/// - 在 Java 應用程序的一次執行過程中,每當對同一對象多次調用它時,只要在對象的`equals`比較中使用的信息沒有被修改,`hashCode`方法就必須始終返回相同的整數。這個整數在同一應用程序的不同執行之間不需要保持一致。
/// - 如果根據[equals][#equals(Object)]方法兩個對象相等,那么對這兩個對象中的每一個調用`hashCode`方法必須產生相同的整數結果。
/// - 如果根據[equals][#equals(Object)]方法兩個對象不相等,那么對這兩個對象中的每一個調用`hashCode`方法并不要求必須產生不同的整數結果。然而,程序員應該意識到,為不相等的對象產生不同的整數結果可能會提高哈希表的性能。
///
/// @implSpec
/// 在合理可行的范圍內,由類`Object`定義的`hashCode`方法為不同的對象返回不同的整數。
///
/// @return 這個對象的哈希碼值。
/// @see java.lang.Object#equals(java.lang.Object)
/// @see java.lang.System#identityHashCode
JEP 471—— 棄用 sun.misc.Unsafe 中的內存訪問方法以準備移除
正如其名稱所示,Unsafe 是一個內部且不受支持的 JDK 類,調用它是不安全的。由于歷史原因,許多底層框架使用 Unsafe 進行更快的內存訪問。由于有了 VarHandle API(JEP 193,自 Java 9 起)和外部函數與內存 API(JEP 454,自 Java 22 起)功能,現在有了 Unsafe 內存訪問方法的替代品,它們同樣強大,但更安全且更受支持。總共有超過 Unsafe 的 87 個方法中的 79 個受到影響,這使我們更接近可以棄用并刪除整個類的地步!
棄用這些方法清楚地表明是時候使用這些替代品了!然而,我們大多數人不應該看到這些變化,因為除了在框架或庫中,Unsafe 很少在其他地方被使用。
這些方法將逐步被降級和棄用:
階段 1:在 Java 23(這個 JEP)中棄用。
階段 2:在 Java 24 或 25 中如果在運行時使用則記錄警告。
階段 3:在 Java 26 或更高版本中默認拋出異常(行為可通過命令行選項修改)。
階段 4 和 5:在 Java 26 之后移除這些方法(先移除堆上內存訪問方法,然后移除堆外內存訪問方法)。
JEP 474——ZGC:默認情況下的分代模式
ZGC 是一種垃圾收集器,旨在支持非常大的堆(數 TB)且暫停時間非常短(毫秒級)。
通過 JEP 439 在 Java 21 中添加分代堆使其能夠在消耗更少資源的同時支持不同的工作負載。
現在分代模式是默認模式。
JEP 476—— 模塊導入聲明(預覽)
在 Java 中,你可以導入:
用語句import java.util.*;
導入一個包中的所有類。
用語句import java.util.Map;
導入單個類。
用語句import static org.junit.jupiter.api.Assertions.*;
導入一個類的所有靜態方法和變量。
用語句import static org.junit.jupiter.api.Assertions.assertTrue;
導入單個靜態方法或變量。
然而,以前不可能用一個語句導入一個模塊的所有類。現在可以用import module java.base;
語句來實現,這個語句可以在一個語句中導入從java.base
模塊導出的所有包中的所有類,以及java.base
模塊間接需要的模塊中的類。
退出預覽的功能
在 Java 23 中,沒有以前處于預覽(或孵化模塊)的功能退出預覽或孵化。
當然,除了我在介紹中提到的字符串模板功能,它已從預覽中移除且不知去向。
仍處于預覽的功能
以下功能仍處于預覽(或在孵化模塊中)。
JEP 466—— 類文件 API:第二次預覽,用于解析、生成和轉換 Java 類文件的標準 API。根據用戶反饋進行了改進。在 Java 23 中,JDK 繼續向使用這個新 API 遷移。
JEP 469—— 向量 API:第八次孵化,用于表示向量計算的 API,在運行時編譯為支持的 CPU 架構的向量指令。沒有變化:JEP 同意向量 API 將繼續處于孵化狀態,直到 Valhalla 項目的功能作為預覽可用。這是預期的,因為向量 API 將能夠利用 Valhalla 項目預期的性能和內存表示改進。
JEP 473—— 流收集器:第二次預覽,通過支持自定義中間操作增強了 Stream API。沒有變化。
JEP 477—— 隱式聲明的類和實例主方法:第三次預覽,通過允許在隱式類(無需聲明)和void main()
實例方法中定義簡單程序,簡化了簡單程序的編寫。有兩個變化:隱式類自動導入新的java.io.IO
類的三個靜態方法print(Object)
、println(Object)
和readln(Object)
,并且它們根據需要自動導入java.base
模塊中的包中的類。
JEP 480—— 結構化并發:第三次預覽,一個新的 API,通過允許將多個并發任務視為單個處理單元,簡化了多線程代碼的編寫。沒有變化。
JEP 481—— 作用域值:第三次預覽,允許在線程內部和線程之間共享不可變數據。有一個小變化。
JEP 482—— 靈活的構造函數體:第二次預覽,一個允許在父構造函數之前調用指令的功能,只要它們不訪問當前正在創建的實例。構造函數現在可以在顯式調用構造函數之前初始化同一類的字段。
雜項
對 JDK 進行了各種添加:
隨著 JEP 477—— 隱式聲明的類和實例主方法的推出,除了新的IO
類之外,相同的三個方法也被添加到了Console
類中:print(Object)
、println(Object)
和readln(Object)
。
Console
類增加了三個使用帶有區域設置格式化的字符串的新方法:format(Locale, String, Object)
、printf(Locale, String, Object)
和readLine(Locale, String, Object)
。
Console.readPassword(Locale, String, Object)
:與Console.readPassword(String, Object)
相同,但接受一個區域設置作為參數用于字符串本地化。
Inet4Address.ofPosixLiteral(String)
:根據以 POSIX 兼容形式inet_addr
提供的 IPv4 地址的文本表示創建一個Inet4Address
。
java.text.NumberFormat
及其后代增加了setStrict(boolean)
和isScript()
方法,可用于更改格式化模式;默認模式是嚴格模式。
Instant.until(Instant)
:計算到另一個Instant
的持續時間。
以下方法已被移除,它們已被標記為要刪除,并在以前的版本中已被降級為拋出異常:
內部變化、性能和安全性
并行垃圾收集器(Parallel GC)對其完全垃圾收集算法進行了重新實現,以使用更經典的并行標記 - 清除 - 壓縮算法。這與 G1 垃圾收集器使用的算法相同,在某些特定情況下優化了性能,在使用并行垃圾收集器時減少了 1.5% 的堆使用量。在垃圾收集器方面還進行了其他更改,可以在 Thomas Schatzl 的這篇文章中找到:
《JDK 23 G1/Parallel/Serial GC 更改》。
我還沒有注意到其他值得注意的變化,但如果我發現更多變化,我會更新這篇文章。
JFR 事件
沒有新的 Java 飛行記錄器(JFR)事件。
你可以在頁面 “JFR 事件” 上找到此版本的 Java 支持的所有 JFR 事件。
結論
這個新版本的 Java 在新功能方面相當稀少,目前正在開發的功能中很少有退出預覽的。
盡管在模式匹配中對基本類型的支持以及在 JavaDoc 中對 Markdown 的支持是非常有趣的改進,但字符串模板的消失且沒有替代品意味著對這個期待已久的功能的支持還遙遙無期。
要查找 Java 23 中的所有更改,請參考
發行說明。若你想提升Java技能,可關注我們的
Java培訓課程。