一、前言
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
在不同的形式中穿梭,最終,讓開發者擁有更多自由,能夠用最適合的方式,表達他們的程式碼。
沒有留言:
張貼留言