2025年1月23日 星期四

Java 7 to 17 switch

一、前言

一、前言

返回目錄

switch 語句在程式設計中用於多路條件判斷,自 Java 誕生以來一直是一個重要的控制流程結構。然而,在 Java 7 及之前的版本中, switch 語句的功能和語法相對比較受限,導致程式碼可能顯得冗長且不易讀。隨著 Java 的不斷發展,從 Java 7 開始到 Java 17, switch 語句經歷了多次的改進和擴展,最終演變為更強大、更具表達力的 switch 表達式。本文將詳細分析從 Java 7 到 Java 17 switch 表達式的演進過程,並提供相關的範例和說明。

目錄



二、Java 7 中的 switch 語句

返回目錄

在 Java 7 中, switch 語句的基本形式如下:

  • 限制 :Java 7 中的 switch 語句只能接受整數( int byte short char )和 String 類型的表達式作為判斷的依據。
  • break 的必要性 :每個 case 區塊通常都需要 break 語句來防止執行流程繼續往下執行到下一個 case 區塊。如果缺少 break ,可能會造成程式邏輯錯誤,這被稱為 "fall-through" 行為,雖然有時可以利用此行為,但通常被認為容易導致錯誤。
  • 冗長的程式碼 :當需要執行相同程式碼的情況下,需要重複撰寫相同的 case

範例:

執行結果:

三、Java 14 中的正式特性 (JEP 361) - switch 的進化與融合

返回目錄

在 Java 14 中, switch 表達式 (JEP 361) 成為正式功能,這是一個重要的里程碑,代表 Java 語言在語法表達能力上又邁進了一步。JEP 361 不僅延續了先前版本的改進,更將 switch 的靈活性推向新的高度,讓開發者可以根據不同情境,自由選擇最合適的用法。

(3.1) JEP 361 的核心目標

返回目錄

JEP 361 的主要目標是將 switch 從單純的控制流程語句,轉變為更強大、更具表達能力的工具。它不僅僅是讓 switch 可以返回值,更重要的是提供一個可以同時扮演「語句」和「表達式」角色的 switch 。核心改進如下:

  • switch 的雙重身分: switch 可以作為表達式 (expression) 或語句 (statement) 使用。

    • 表達式 (Expression): 可以直接返回一個值,將返回值賦給變數或作為其他表達式的一部分。
    • 語句 (Statement): 如同傳統 switch 語句一樣,根據不同情況執行程式碼區塊,不一定需要返回數值。
  • 傳統與現代的融合: 無論是作為表達式還是語句, switch 都支援傳統的 case L: 語法 (帶 fall-through) 和現代的 case L -> 語法 (不帶 fall-through)。

  • case L -> 的多重用途: case L -> 不僅能接單一表達式,也能接程式碼區塊。在程式碼區塊中, switch 表達式必須使用 yield 來返回值。

(3.2) 正式特性帶來的優勢

返回目錄

  1. 更廣泛的適用性:

    switch 不僅可以作為表達式直接返回值,也可以像傳統語句一樣執行程式碼區塊,讓它可以應用於更廣泛的場景。

  2. 更簡潔的程式碼:

    switch 表達式讓程式碼更簡潔,使用 -> 語法,減少了繁瑣的 case break ,大幅提升程式碼可讀性。

  3. 避免 "fall-through" 錯誤:

    -> 語法直接避免了因忘記寫 break 而導致的 "fall-through" 錯誤,這一直是 Java 開發者長期以來的痛點。

  4. 更好的表達力:

    switch 作為表達式,可以更好地融入函數式編程風格,例如與 Stream API 結合使用。

  5. 提高開發效率:

    簡潔的語法和更少的潛在錯誤可以提高開發效率,讓開發者更專注於業務邏輯。

(3.3) break yield 的使用場景與注意事項

返回目錄

雖然 -> 語法在大多數情況下無需使用 break yield ,但當 case 區塊需要執行多行程式碼,且需要從 switch 中返回值時,仍需要使用 yield (在 switch 表達式中) 或 break (在 switch 語句中)。

  • 區塊 case 的返回值:

    • 使用 case L -> 單行可直接變成返回值(不搭配大括號 {} )。
    • switch 中,使用大括號 {} 包裹的 case 區塊, 必須通過 yield 返回值 (在 JEP 354 中引入)。
  • break 語句的專用:

    • switch 中, break 專門用於跳出 switch 語句 ,搭配程式碼區塊時, 可以 帶一個標籤,但 不能 用於返回值。
  • yield 的使用: yield 語句僅用於 switch 表達式中,在 case 區塊中返回值。

  • yield 為 restricted identifier: yield 不再是一個關鍵字 (keyword), 而是一個 restricted identifier,因此 不能 定義一個類別叫 yield ,但是如果定義一個叫 yield 的方法,在 switch 表達式內就會造成混淆,編譯器會優先選擇 yield 語句,而非方法名稱。

(3.4) 範例解析

返回目錄

讓我們來看看 Java 14 的 switch 範例:

(3.4.1) 返回值

返回目錄

執行結果:

程式碼解析:

  • String dayType = switch (day) { ... }; : switch 作為表達式,其值將會賦值給 dayType 變數。

  • case 1, 2, 3, 4, 5 -> { ... } : 多個常數 1, 2, 3, 4, 5 共用同一個 case 區塊,表示這些天都是工作日。

    • System.out.println("It's a weekday"); : 印出提示訊息。
    • yield "Weekday"; : 使用 yield 語法返回字串 "Weekday" ,並結束 switch 表達式的執行。
  • case 6, 7 -> { ... } : 多個常數 6, 7 共用同一個 case 區塊,表示這些天是週末。

    • System.out.println("It's a weekend"); : 印出提示訊息。
    • yield "Weekend"; : 使用 yield 語法返回字串 "Weekend"
  • default -> ... : day 的值不符合任何 case 時,會執行 default 區塊。

    • -> "Invalid day"; : 返回字串。
  • System.out.println("Day type: " + dayType); : 印出 dayType 的值。

(3.4.2) break 標籤 (labeled break):

返回目錄

break 語句可以帶一個標籤,用於跳出被標籤的程式碼區塊。這在巢狀迴圈或 switch 語句中特別有用。

flowchart TD A[開始] --> B(outerLoop: i=0 to 4); B --> C(innerLoop: j=0 to 4); C --> D{switch i}; D -- i是1 --> E[印: switch i=1, 跳出內迴圈]; E --> F[break innerLoop]; D -- i不是1 --> G{if i是2且j是2}; G -- true --> H[跳出外迴圈: i=2, j=2]; H --> I[break outerLoop]; G -- false --> J{if j是3}; J -- true --> K[印: 跳出內迴圈, j=3]; K --> L[break innerLoop]; J -- false --> M[印: i=, j=]; M --> C; F --> B; L --> B; I --> N[印: 外迴圈結束]; N --> O[結束]; C --> B; B --> N;

執行結果:

執行流程分析:

  1. 外層迴圈 (outerLoop):
    • 外層迴圈從 i = 0 迭代到 i = 4。
  2. 內層迴圈 (innerLoop):
    • 對於每個 i 的值,內層迴圈從 j = 0 迭代到 j = 4。
  3. switch 語句 (switch (i)):
    • 當 i 的值為 1 時,case 1 會被執行,印出訊息 switch i=1, so breaking inner loop.,並使用 break innerLoop; 跳出內層迴圈。
  4. 條件判斷 (if (i == 2 && j == 2)):
    • 當 i 的值為 2 且 j 的值為 2 時,會印出 Breaking outer loop at i=2, j=2,並使用 break outerLoop; 跳出外層迴圈。
  5. 條件判斷 (if (j == 3)):
    • 當 j 的值為 3 時,會印出 Breaking inner loop at j=3,並使用 break innerLoop; 跳出內層迴圈。
  6. 輸出 i 和 j 的值:
    • 當 switch 語句和兩個條件判斷都沒執行時,則會印出 i= 和 j= 的值。
  7. 外層迴圈結束:
    • 當程式碼跳出外層迴圈後,會印出 Outer loop is finished。

(3.4.3) 與 Stream API 結合的範例

返回目錄

switch 表達式不僅能單獨使用,還可以與 Java 8 引入的 Stream API 結合,以更簡潔和函數式的方式處理資料。以下是一個範例,示範如何使用 switch 表達式和 Stream API 來將一組字串轉換為特定的整數代碼:

執行結果:

程式碼解析:

  1. 建立字串列表: 建立一個包含多個水果名稱的字串列表 words
  2. 使用 Stream API: 使用 words.stream() 創建一個字串的流。
  3. map 操作:
    • 使用 map 方法將每個字串映射成一個整數代碼。
    • map 函數中,使用 switch 表達式根據單字的值返回不同的代碼:
      • 如果單字是 "apple" "banana" "cherry" "date" ,則返回相應的整數代碼。
      • 否則,返回 -1 ,表示未知單字。
  4. collect 操作: 使用 collect(Collectors.toList()) 將映射後的整數代碼收集到一個列表中。
  5. 輸出結果: 印出 codes 列表,其中包含每個字串對應的整數代碼。

實務說明:

這個範例演示了如何將 switch 表達式與 Stream API 的 map 操作結合使用。這種方式非常適合於需要根據不同輸入值,轉換成不同輸出值的場景。

  • 簡潔易讀: 使用 switch 表達式讓程式碼更加簡潔易讀,並且避免了傳統的 if-else 巢狀判斷。
  • 函數式風格: switch 表達式可以自然地融入到 Stream API 的函數式風格中,提高程式碼的可讀性和維護性。
  • 高效率: 可以搭配 Stream API 的平行處理功能,讓程式碼更有效率。

更進階的應用:

  • 你可以將此範例擴展到更複雜的場景,例如處理不同類型的輸入,或與資料庫或其他資料來源結合。
  • 可以使用 Optional 處理 switch 表達式中可能的空值或例外情況。
  • 搭配 record 來更方便的處理資料。

(3.5) 與 Java 7 switch 的比較

返回目錄

與 Java 7 的 switch 語句相比,Java 14 的 switch 更靈活、更強大,也更簡潔。

特性 Java 7 switch 語句 Java 14 switch (JEP 361)
語法 : break -> : 和可選的 break / yield
返回值 無法直接返回值 可以直接返回值
"fall-through" 可能發生 使用 -> 時不會發生
多個常數的 case 需要多個重複 case 可以使用逗號分隔
可讀性 較差 較好
表達能力 較弱 較強
靈活性 較差 更靈活,可作為語句或表達式

四、總結

返回目錄

從 Java 7 到 Java 17, switch 語句經歷了從傳統的語法到表達式的轉變,其核心改進包括:

  • 簡化的語法: 使用 -> 符號,移除了繁瑣的 : break 語句。
  • 表達式能力: 可以直接返回值,並且可以和變數賦值連用,使程式碼更簡潔。
  • 減少錯誤: 消除了因忘記 break 導致的 "fall-through" 問題。

透過這些演進, switch 從單純的條件判斷語句,變成了更强大、更具表達力的程式語言工具,使 Java 程式碼更加清晰易讀,提高了開發效率。開發者應當熟悉這些改進,以便在專案中充分利用 switch 表達式的優勢。

附錄: switch 演進歷程

返回目錄

switch 經歷許多預覽版本,有些被腰斬,有些轉成正式版本,這邊擺放一些說明發展的過程,當作故事閱讀即可。

switch 的進化史詩:從管家到多重宇宙的旅程

返回目錄

在 Java 王國中, switch 語句確實是一位老實可靠的管家,但它身上總帶著一些無法忽視的缺點。開發者常常需要與它冗長的語法搏鬥,每次都得寫下 case break ,就像在老舊的迷宮中行走,一不小心就會忘記 break ,結果導致意料之外的程式碼執行。

而當 Java 世界準備迎接模式匹配 (pattern matching) 的到來時, switch 的這些缺點顯得更加礙事。現有的 switch 語句,只是一個單純的控制流結構,無法像表達式一樣傳回一個數值,當開發者需要根據不同的值計算出不同的結果時,往往需要額外的變數和跳轉指令,就像繞著遠路去達到目的地一樣,重複、繁瑣且容易出錯。

這時, switch 的進化之旅正式展開,它由三個重要的篇章組成:JEP 325、JEP 354 和 JEP 361。

第一章:變革的起點 — JEP 325, switch 的雙面面具 (Java 12)

返回目錄

這時,JEP 325 出現了,它不只是單純地修補 switch 的缺點,更是為 switch 創造了一個全新的面貌。JEP 325 並沒有捨棄 : break !而是為 switch 增加了新的選項,讓它如同一個擁有雙重面具的角色:

  • 傳統面具 (Traditional): 仍然支援使用 case L: break 的傳統語法,保留了既有的 fall-through 行為,就像 switch 過去一直以來使用的樣子。

  • 簡化面具 (Simplified): 引入了 case L -> 的新語法,用 -> 取代了 : break , 當程式碼符合條件時,只會執行箭頭右邊的程式碼,沒有 fall-through 的問題。 -> 後面可以跟著一個表達式、一個程式碼區塊或一個 throw 語句。

這個雙重面具,讓 switch 可以同時扮演「語句」和「表達式」的角色。當 switch 作為語句使用時,可以選擇使用傳統語法或簡化語法。而當 switch 作為表達式使用時,則必須採用簡化語法,並且要能傳回一個數值。

JEP 325 的主要目標如下:

  • 表達式化 (Expression): switch 轉換成可以傳回數值的表達式,以便更方便地計算結果。
  • 簡化控制流 (Simplified Control Flow): 使用 -> 語法來避免 fall-through 的問題,減少 break 的使用,讓程式碼更清晰易懂。
  • 引入多值匹配: 允許在單個 case 中匹配多個數值, 例如 case MONDAY, FRIDAY, SUNDAY -> System.out.println(6);
  • 規範區塊作用域 (Scope): 使用 -> 語法時, case 的程式碼區塊會被限制在自己的作用域中,避免傳統 switch 區塊中變數名衝突的問題。

舉例來說,程式碼

可以用新的 switch 表達式寫成:

或是用 switch 表達式:

雖然 JEP 325 只是預覽功能,但它已經為 switch 的未來開啟了大門,它如同潛藏在 Java 世界的一股變革之力。

第二章:進化的試煉 — JEP 354, switch 的第二次預覽與 yield 的誕生 (Java 13)

返回目錄

在 JEP 325 的變革之後, switch 的雙面面具已經展現了它潛藏的威力。開發者們開始嘗試使用新的 switch 表達式,並回饋了寶貴的意見。其中一個重要的反饋,是關於在 switch 表達式中如何處理多個指令並返回數值的情況。

在 JEP 325 的預覽版中,開發者可以使用程式碼區塊 {} 來包含多個指令,並使用帶有數值的 break 語句來返回結果。然而,這種方式並不夠直觀,並且與傳統的 break 語句有所混淆。於是,JEP 354 這個新的篇章,準備對 switch 表達式進行更精細的調整。

重要提醒: JEP 354 的正式名稱是 "Switch Expressions (Second Preview)" ,這代表它是 Java 13 的 第二個預覽版本 ,而不是最終的正式版。這意味著它仍然帶有實驗性質,並且在正式版本推出前,仍然有可能變更。

在第二個預覽版中,最大的改變就是: yield 語句取代了帶有數值的 break 語句,來在 switch 表達式中返回數值。

JEP 354 的主要目標如下:

  • 統一的返回方式: yield 語句來取代帶數值的 break ,統一在 switch 表達式中返回數值的語法。
  • break 的專用: 保留 break 關鍵字,讓它專門負責跳出 switch 語句,不再負責傳回值。
  • yield 的清晰語義: yield 明確表達在 switch 表達式中「傳回值」的意涵,避免與 break 的跳出行為混淆。
  • 加強控制流分析: 確保 switch 表達式的所有分支都能返回數值,或者拋出例外,讓程式碼更安全可靠。

例如,在 JEP 325 中,可能會這樣寫:

而在 JEP 354 中,則會改寫成:

yield 的出現,不僅讓語法更清晰,也讓 switch 表達式更符合表達式的直觀語義:計算一個值,並將其返回。 yield 就像一個小型的出口,它不僅能讓程式碼返回數值,還能讓程式碼在返回之前執行其他額外的邏輯。

JEP 354 與 JEP 325 的繼承關係

  • 保留 -> 語法: JEP 354 延續了 JEP 325 中的 case L -> 語法,繼續提供簡化的 switch 控制流。
  • 保留多值匹配: 同樣延續了多值匹配的特性,讓 case 可以匹配多個數值。
  • 修正 break : 改用 yield 來返回數值,避免與 break 產生混淆。

JEP 354 的核心改動:

  • yield 的誕生: yield 關鍵字來傳回 switch 表達式的值,取代帶有數值的 break
  • 預覽版本的調整: 根據 JEP 325 的回饋,進行語法和語義上的微調。
  • 明確的語法分割: break yield 各自負責 switch 的不同行為,一個負責跳出 switch 語句,一個負責傳回 switch 表達式的值。

JEP 354 (Second Preview) 就像是一場精雕細琢的試煉,它讓 switch 表達式更加完善,也為它成為 Java 正式功能鋪平了道路。它如同對 switch 表達式注入了新的生命力,讓它在 Java 世界中更加耀眼。

這個階段的重點是,開發者需要了解 yield 語句的使用方式,以及它與 break 語句的區別。JEP 354 雖然只是一個預覽版本,但它已經展現了 switch 表達式未來的模樣。

接下來, switch 將會迎接最終的挑戰,並展現它在 Java 14 中多重宇宙的靈活運用。

第三章:融合與解放 — JEP 361, switch 的多重宇宙與自由 (Java 14)

返回目錄

在 JEP 354 之後, switch 表達式已經逐漸成熟,它如同一個嶄新的魔法工具,賦予 Java 開發者更強大的能力。然而,開發者們在實際應用中發現,並非所有情況都適合使用 switch 表達式,有時候,他們需要的僅僅是傳統的 switch 語句,根據不同的情況,執行一些操作,而不需要傳回任何數值。此外,將所有的 switch 語句都轉換為 switch 表達式,也並非總是都能簡化程式碼。

於是,JEP 361 應運而生,它的目標,不是要取代舊的語法,而是要擴展 switch 的可能性,讓它更加靈活,更能適應不同的需求。JEP 361 就像是給 switch 開啟了一個多重宇宙,讓它可以同時扮演表達式和語句的角色,並且能使用傳統或簡化的語法。

JEP 361 的核心理念:

  • 多重宇宙: switch 同時支持兩種形式: switch 表達式 (expression) 和 switch 語句 (statement)。
  • 傳統與現代: 兩種形式都支持傳統的 case L: 和簡化的 case L -> 語法。
  • 自由選擇: 開發者可以根據不同的情境,選擇最合適的 switch 形式。

JEP 361 的主要內容:

  1. switch 語句 (Statement) 的復活: JEP 361 允許開發者使用傳統的 switch 語法,就像在 Java 12 之前一樣。這表示開發者可以使用 case L: break ,執行具有 fall-through 行為的程式碼區塊。

  2. switch 語句也可以使用 case L -> : JEP 361 也允許 switch 語句使用 case L -> 語法。當 case L -> 後面跟著一個程式碼區塊時,這個區塊必須使用 break 返回數值,就像在 switch 表達式中使用 yield 一樣。

  3. 混合使用: 開發者可以在同一個程式碼中,混合使用 switch 語句和 switch 表達式,根據不同的需求,選擇最合適的形式。

  4. yield 為 restricted identifier : yield 不再是一個關鍵字 (keyword), 而是一個 restricted identifier, 因此可以定義一個類別叫 yield ,但是如果定義一個叫 yield 的方法就會造成混淆,編譯器會優先選擇 yield 語句。

JEP 361 的意義:

  • 靈活度: switch 語法更靈活,可以根據不同的情境,選擇最合適的語法形式。
  • 兼容性: 可以更容易地將現有的 switch 語句遷移到新的 switch 語法。
  • 自由選擇: 開發者可以自由地選擇使用 switch 語句或 switch 表達式,並根據自己的喜好選擇使用傳統或簡化的語法。

JEP 361 的出現,讓 switch 的進化旅程更加完整。它不再是單純的控制流或表達式,而是一個更具彈性的工具,可以滿足不同的開發需求。它如同一個多重宇宙,讓 switch 在不同的形式中穿梭,最終,讓開發者擁有更多自由,能夠用最適合的方式,表達他們的程式碼。

返回目錄

沒有留言:

張貼留言