一、前言
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) 正式特性帶來的優勢
-
更廣泛的適用性:
switch不僅可以作為表達式直接返回值,也可以像傳統語句一樣執行程式碼區塊,讓它可以應用於更廣泛的場景。 -
更簡潔的程式碼:
switch表達式讓程式碼更簡潔,使用->語法,減少了繁瑣的case和break,大幅提升程式碼可讀性。 -
避免 "fall-through" 錯誤:
->語法直接避免了因忘記寫break而導致的 "fall-through" 錯誤,這一直是 Java 開發者長期以來的痛點。 -
更好的表達力:
switch作為表達式,可以更好地融入函數式編程風格,例如與 Stream API 結合使用。 -
提高開發效率:
簡潔的語法和更少的潛在錯誤可以提高開發效率,讓開發者更專注於業務邏輯。
(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;
執行結果:
執行流程分析:
-
外層迴圈 (outerLoop):
- 外層迴圈從 i = 0 迭代到 i = 4。
-
內層迴圈 (innerLoop):
- 對於每個 i 的值,內層迴圈從 j = 0 迭代到 j = 4。
-
switch 語句 (switch (i)):
- 當 i 的值為 1 時,case 1 會被執行,印出訊息 switch i=1, so breaking inner loop.,並使用 break innerLoop; 跳出內層迴圈。
-
條件判斷 (if (i == 2 && j == 2)):
- 當 i 的值為 2 且 j 的值為 2 時,會印出 Breaking outer loop at i=2, j=2,並使用 break outerLoop; 跳出外層迴圈。
-
條件判斷 (if (j == 3)):
- 當 j 的值為 3 時,會印出 Breaking inner loop at j=3,並使用 break innerLoop; 跳出內層迴圈。
-
輸出 i 和 j 的值:
- 當 switch 語句和兩個條件判斷都沒執行時,則會印出 i= 和 j= 的值。
-
外層迴圈結束:
- 當程式碼跳出外層迴圈後,會印出 Outer loop is finished。
(3.4.3) 與 Stream API 結合的範例
switch
表達式不僅能單獨使用,還可以與 Java 8 引入的 Stream API 結合,以更簡潔和函數式的方式處理資料。以下是一個範例,示範如何使用
switch
表達式和 Stream API 來將一組字串轉換為特定的整數代碼:
執行結果:
程式碼解析:
-
建立字串列表:
建立一個包含多個水果名稱的字串列表
words。 -
使用 Stream API:
使用
words.stream()創建一個字串的流。 -
map操作:-
使用
map方法將每個字串映射成一個整數代碼。 -
在
map函數中,使用switch表達式根據單字的值返回不同的代碼:-
如果單字是
"apple"、"banana"、"cherry"或"date",則返回相應的整數代碼。 -
否則,返回
-1,表示未知單字。
-
如果單字是
-
使用
-
collect操作: 使用collect(Collectors.toList())將映射後的整數代碼收集到一個列表中。 -
輸出結果:
印出
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 的主要內容:
-
switch語句 (Statement) 的復活: JEP 361 允許開發者使用傳統的switch語法,就像在 Java 12 之前一樣。這表示開發者可以使用case L:和break,執行具有 fall-through 行為的程式碼區塊。 -
switch語句也可以使用case L ->: JEP 361 也允許switch語句使用case L ->語法。當case L ->後面跟著一個程式碼區塊時,這個區塊必須使用break返回數值,就像在switch表達式中使用yield一樣。 -
混合使用: 開發者可以在同一個程式碼中,混合使用
switch語句和switch表達式,根據不同的需求,選擇最合適的形式。 -
yield為 restricted identifier :yield不再是一個關鍵字 (keyword), 而是一個 restricted identifier, 因此可以定義一個類別叫yield,但是如果定義一個叫yield的方法就會造成混淆,編譯器會優先選擇yield語句。
JEP 361 的意義:
-
靈活度:
讓
switch語法更靈活,可以根據不同的情境,選擇最合適的語法形式。 -
兼容性:
可以更容易地將現有的
switch語句遷移到新的switch語法。 -
自由選擇:
開發者可以自由地選擇使用
switch語句或switch表達式,並根據自己的喜好選擇使用傳統或簡化的語法。
JEP 361 的出現,讓
switch
的進化旅程更加完整。它不再是單純的控制流或表達式,而是一個更具彈性的工具,可以滿足不同的開發需求。它如同一個多重宇宙,讓
switch
在不同的形式中穿梭,最終,讓開發者擁有更多自由,能夠用最適合的方式,表達他們的程式碼。
沒有留言:
張貼留言