JDK 23 功能預覽:JEP 455 基礎型別的模式匹配

JEP 455: Primitive Types in Patterns, instanceof, and switch

在 Java 的演進過程中,模式匹配一直是項重要的特性,但在處理基礎型別(primitive types)時卻存在許多限制。JDK 23 中的 JEP 455 為 Java 帶來了基礎型別模式匹配(pattern matching)的支援,讓開發人員能夠更優雅地處理型別轉換和檢查。

它不僅擴展了 instanceof 運算子的功能,也增強了 switch 的能力去處理所有基礎型別。這些改進大幅提升了 Java 程式碼的表達能力和安全性,同時也減少了冗長且容易出錯的程式碼。

前言

在傳統的 Java 開發中,處理基礎型別的轉換常常需要寫出冗長的程式碼。例如,當我們需要將一個 int 轉型為 byte 時,必須先進行數值範圍檢查,然後再執行明確的轉型操作。這不僅增加了程式碼的複雜度,還容易產生錯誤。

更重要的是,在處理基礎型別轉換時,可能會發生無聲的資料遺失,但現有的 Java 語法並沒有提供優雅的方式來處理這種情況。另外,switch 陳述式的限制也一直是開發人員的痛點。在此之前,switch 只能處理有限的幾種基礎型別,無法處理 booleanfloatdoublelong 型別,這限制了其應用場景。

為什麼需要基礎型別的模式匹配?

第一個限制是 switch 的模式匹配(JEP 441)不支援基礎型別的模式,僅支援指定參考類型(reference type)的模式匹配,例如 case Integer icase String s(自 Java 21 起,也支援記錄模式 JEP 440 用於 switch)。

另一個限制是記錄(record)的模式匹配對基礎型別的支援有限。當記錄類別中的某個欄位是基礎型別時,其模式匹配必須精確說明該值的型別。這對於開發人員來說是不方便的,並且與 Java 語言中存在的自動轉型不一致。例如,假設我們希望表示 JSON 數據:

sealed interface JsonValue {
    record JsonString(String s) implements JsonValue { }
    record JsonNumber(double d) implements JsonValue { }
    record JsonObject(Map<String, JsonValue> map) implements JsonValue { }
}

JSON 不區分整數和非整數,因此 JsonNumber 使用 double 表示數字以獲得最大的靈活性。但是,我們在創建 JsonNumber 記錄時不需要傳遞 double;我們可以傳遞一個 int,例如 30,Java 編譯器會自動將 int 擴展為 double

var json = new JsonObject(Map.of("name", new JsonString("John"),
                                 "age",  new JsonNumber(30)));

遺憾的是,如果我們想用記錄模式分解 JsonNumber,Java 編譯器就沒那麼好說話了。由於 JsonNumber 是用 double 宣告的,我們必須將 JsonNumber 分解為 double,然後手動指定轉型為 int

if (json instanceof JsonObject(var map)
    && map.get("name") instanceof JsonString(String n)
    && map.get("age")  instanceof JsonNumber(double a)) {
    int age = (int) a;  // 無法避免的(並且可能是有損的!)指定轉型
}

換句話說,基礎型別的模式匹配可以用在記錄類別上,但是其模式匹配裡的基礎型別必須與記錄欄位的基礎型別相同。無法通過 instanceof JsonNumber(int age) 來分解 JsonNumber 並讓編譯器自動將 double 窄化為 int

這項限制是因為窄化轉型有可能會損失訊息,意即 double 的值可能太大或精度太高,導入無法安全地窄化成為 int 變數。然而,模式匹配的其中一個好處是它可以通過不匹配來拒絕非法值。也就是說,如果 JsonNumberdouble 無法安全地窄化成為 int,那麼 instanceof JsonNumber(int age) 可以返回 false,讓程式在不同的條件分支中處理大型 double 數值即可。

JEP 455 概觀

本功能解除了基礎型別在模式匹配中存在的幾個限制。處理基礎型別和物件型別的程式碼將會被簡化,不再需要特殊處理,使得這兩種型別可以用相同的方式應對。由於現在可以自動處理條件式轉型(數值必須被檢查以確保它對目標型別有效),因此允許 instanceof 接受基礎型別的話可以進一步減少程式碼數量。

它增強了 Java 的模式匹配功能,並擴展 instanceofswitch,使其能夠在所有模式匹配的上下文中使用基礎型別:

  • 基礎型別模式的增強:允許在模式匹配中適用更廣泛的候選基礎型別,例如 record Test(double d) 可以使用 x instanceof Test(int i) 去判定 d 值是否能轉型成 int
  • instanceof 的擴展:允許 instanceof 運算符與基礎型別一起使用,並且可以安全轉換,例如 if (x instanceof int i) { ... }
  • switch 的擴展:允許 switch 表達式處理所有基礎型別,包括 booleanfloatdoublelong,例如 case byte b -> ...

優點

  • 使模式匹配在處理基礎型別和參考型別時更加一致,減少了開發人員需要記住的特殊情況,並且實現統一的資料處理方式
  • 通過在 instanceofswitch 中使用基礎型別模式,可避免不必要的裝箱和拆箱操作,從而提高程式碼的效能,並消除不安全轉型而導致資料損失的風險
  • 擴展了 instanceofswitch 的功能,使開發人員能夠編寫更具表達力和可讀性的程式碼

缺點

  • 對於不熟悉模式匹配的開發人員來說,引入基礎型別模式可能會增加複雜度和學習曲線
  • 雖然在某些情況下可以避免裝箱和拆箱操作,但在其他情況下,使用基礎型別模式可能會導致額外的效能影響

功能說明

讓我們來看看這項新功能如何改善程式碼。

switch 陳述句

透過在 switch 中支援基礎型別模式,我們可以改進 switch 表達式:

// 舊方式
switch (x.getStatus()) {
    case 0 -> "okay";
    case 1 -> "warning";
    case 2 -> "error";
    default -> "unknown status: " + x.getStatus();
}

通過將 default 子句修改成為具有基礎型別模式的 case 子句,該模式公開匹配的值:

// 新方式
switch (x.getStatus()) {
    case 0 -> "okay";
    case 1 -> "warning";
    case 2 -> "error";
    case int i -> "unknown status: " + i;
}

支援基礎型別模式還允許 guard 檢查匹配的值:

switch (x.getYearlyFlights()) {
    case 1 -> normal();
    case 2 -> issueDiscount();
    case int i when i >= 100 -> issueGoldCard();
    case int i -> issueSilverCard();  // 剩下的條件:when i > 2 && i < 100 ...
}

instanceof 陳述句

原本將 int 值轉型為 byte 是通過顯式的指定轉型執行的,但轉型有可能會損失訊息,因此必須在轉型前先進行繁瑣的範圍檢查:

// 舊方式
int value = 42;
if (value >= -128 && value <= 127) {
    byte b = (byte) value;
    // 使用 b
}

instanceof 中的基礎型別模式匹配會檢查該轉型是否會損失訊息,以避免開發人員近三十年來一直手動編寫的繁瑣範圍檢查。換句話說,instanceof 可以檢查數值和型別。上面的例子可以重寫如下:

// 新方式
if (value instanceof byte b) {  // 如果可以安全轉型的話即為 true
    // 使用 b,確保沒有資料遺失
}

結語

JEP 455 增強在模式匹配、instanceofswitch 中使用基礎型別,為 Java 開發人員帶來了更優雅、更安全的程式設計方式。透過統一的語法來處理型別檢查和轉型,不僅提高了程式碼的可讀性,也降低了發生錯誤的風險。

隨著這項功能的引入,Java 在型別系統的處理上更加完整和一致。雖然這可能需要一些時間來適應新的語法,但這項改進無疑將為 Java 開發帶來更好的開發體驗和更高的程式碼品質。

本篇文章的內容為老喬原創、二創或翻譯而來。雖已善盡校對、順稿與查核義務,但人非聖賢,多少仍會有疏漏之處難以避免。如果大家有任何問題、建議或指教,都歡迎在底下留言與老喬討論!

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *

5 × three =

返回頂端