Java 語言從 1995 年發表至今 2026 年已經超過 30 個年頭,有些舊專案可能還停留在 Java 6,比較近代一點的會停留在 Java 8、Java 11、Java 17 這幾個 LTS 版本。目前最新的 LTS 是 2025 年 9 月推出的 Java 25,最新穩定版則是 2026 年 3 月發布的 Java 26。這些年間光是 switch 關鍵字的功能就進化許多次,像是加入了 Switch 陳述句(Switch Expressions)、箭頭回傳值、多重數值的 case(Multi-Constant Case)、新的關鍵字 yield,還有 Java 21 正式登場的 Pattern Matching for switch 等,本文將介紹 switch 多了哪些方便好用的新功能。
Update 2026: 原文是2022年寫的「從 Java 7、17 到 Java 20,歷代 Java Switch 關鍵字的進化」,隨著時間演變補充到 Java 26 了。
Java 7
Switch with String case
在Java 7之前switch只能帶入整數或字元,如果帶入String會出現類似這樣錯誤訊息:
test/App.java:27: incompatible types
found : java.lang.String
required: int
switch (url) {
^
1 error
從Java 7開始才能使用String,例如以下範例:
String str = "klab.tw";
switch(str) {
case "klab.tw": return "Blog Home";
case "google.com": return "Search Engine";
default: return "UNKNOW";
}
原理上Java會使用呼叫String.hashCode()將switch參數轉換成int型態,因此switch與case判斷的依然是整數,但是Hash Code有可能會重複,因此編譯器會自動在case內部使用if判斷式搭配String.equals(Object)再判斷一次是否一致,詳細可以參考這篇問答:How switch case with string(come in java 1.7) work internally?。
順道一提 enum 關鍵字產生的列舉型態帶入 switch 的時候也會呼叫 Enum.ordinal() 轉換成整數型態做比較,因此 switch 語句一直都是接受整數(char 也可以看作是一種整數型態)。也因此在傳統寫法下傳給 switch 的 String 或 Enum 都不可以是 null,Java 無法對 null 呼叫 hashCode()、ordinal() 等方法。就算用 default: 來處理也是沒用的,因為程式還來不及執行到 default 就會先拋出 NullPointerException。switch 會有這些限制是因為要在編譯期間進行優化,執行的時候 switch 會比 if-else 的效率來得高。好消息是從 Java 21 開始,搭配 Pattern Matching 的新式寫法已經可以直接在 case 中處理 null 了,後面會介紹。
Java 12到14
從Java 12開始推出許多Switch的預覽功能,Java 13做了一些改進,到Java 14才從預覽版變成正式版。預覽版代表這些功能以後可能還會修改,甚至移除。正式版代表這些功能之後會穩定存在,可以放心使用。
- JEP 325: Switch Expressions (Preview) https://openjdk.org/jeps/325
- JEP 354: Switch Expressions (Second Preview) https://openjdk.org/jeps/354
- JEP 361: Switch Expressions https://openjdk.org/jeps/361
為了方便後續使用程式碼舉例,我們先假設有以下這個enum。
public enum EUrl {
HOME, GOOGLE, BING, OTHER;
}
Statements跟Expressions
先簡單講一下Statements跟Expressions的差別,Statements常見的翻譯為「語句」,是一段用來定義、控制、改變程式的狀態的程式碼。而Expressions常見的翻譯是「陳述句」,是一段有回傳值的程式碼。例如int x = 1 + 2 * 3;是一個Statement,它宣告有一個int變數在記憶體中,並且指定了裡面的值,而且沒有任何回傳值。而等號右邊的1 + 2 * 3是Expression,可以是四則運算、邏輯運算、有回傳值的函數呼叫,最後變成一個值回傳到最左邊。可以參考:https://andyyou.github.io/2016/03/06/expressions-vs-statements-in-js/,雖然是以JavaScript為例講解,但是Statements跟Expressions的基本概念是通用的。
Switch Expressions
原本switch是一段Statement,switch會接受一段Expression,例如switch(num)、switch(1 + 1)、switch(getNumber())等,然後經過case判斷後進行一些控制與操作,不會有回傳值。儘管在case內使用了return關鍵字可以產生類似回傳值的效果,但其實那依然是一個控制行為,代表退出目前所在的method,沒辦法留在method內。
從Java 12開始發展了Switch Expressions,然後在Java 14以正式版推出此功能,讓switch可以成為Expression有返回值,例如以下範例。
String url = switch(eurl) {
case HOME -> "klab.tw";
case GOOGLE -> "google.com";
case BING -> "bing.com";
default -> "UNKNOW";
};
// 注意,最後要加上分號
從範例中可以看見使用case 條件 -> 返回值;,直接將值返回給switch語句前面的變數。以前想要類似功能會使用case 條件: return 返回值;,但是這樣會直接離開Method,只能將switch另外包裝在一個Method內呼叫,在不需要重複使用這段程式碼的情況下多此一舉。
在Visual Studio Code使用低於Java 14的版本使用以上Code會出現「Switch Expressions are supported from Java 14 onwards only Java(1073743545) 」的錯誤提示。
新的關鍵字yield與Case Block
Switch Expression讓程式碼更加簡潔,但有時候我們還是需要一些Statement在case裡面,因此可以加上大括號包起來,要回傳的數值就使用yield關鍵字。例如我們修改上一則範例,在default裡面加上一個System.err的輸出,然後才回傳UNKNOW。
String url = switch(eurl) {
case HOME -> "klab.tw";
case GOOGLE -> "google.com";
case BING -> "bing.com";
default -> {
System.err.println("Unknow Type");
if(defaultUrl != null) {
yield defaultUrl;
} else {
yield "UNKNOW";
}
}
};
在Java 12時基於JEP 325出現一個預覽版的寫法是break VALUE;,但是Java 13基於JEP 354移除掉此寫法,改成上面範例中的新關鍵字yield VALUE;了,原因是break後面還有可能接上label名稱,容易造成混淆。可以參考這篇問答:What does the new keyword “yield” mean in Java 13?。關鍵字yield在Java 14已經變成正式功能了,可以放心使用。
Multi-Constant Case
這個功能是讓一個case後面接好幾個值,例如case 1, 2, 3:或是case 1, 2, 3 ->,從Java 12開始加入預覽版,在Java 14成為正式功能。例如以下範例。
String type = switch(eurl) {
case HOME -> "My Blog https://klab.tw/";
case GOOGLE, BING -> "Search Engine";
default -> {
System.err.println("Unknow Type");
yield "UNKNOW";
}
};
在Visual Studio Code使用低於Java 14的版本使用以上Code會出現「Multi-constant case labels supported from Java 14 onwards only Java(1073743543) 」的錯誤提示。
有些文章稱呼為Multi-values case,但這樣聽起來好像可以是變數,可是switch的case不接受變數型態,這會讓編譯器無法進行編譯時的優化,因此Multi-constant的稱呼比較符合。
在Java這類預先編譯過的語言中switch的效率比if-eise來得高,限制是switch的值必須在編譯階段就確定,因此判斷條件是不確定的變數的情況下只能使用if-eise的方式來達成。另外Java編譯switch的時候會依照每個case的數值的稀疏程度編譯出兩種不同類型的switch,其中case的數值接近產生的緊湊型效率會更高。也因此switch的case不但是常數,還必須是整數,有機會來討論這點。
Java 17 到 20:Pattern Matching 的四代預覽
Java 17 是一個 LTS 版,從這一代開始 switch 關鍵字開始出現模式匹配(Pattern Matching)功能,但在這一版的新功能還是預覽階段。而且 Java 17、Java 18、Java 19、Java 20 連續四代都有一個 switch JEP 預覽,不斷調整語法細節,所以以下篇幅介紹的都是從 Java 17 開始的預覽功能,最後在 Java 21 透過 JEP 441 正式畢業,後面會有專門章節介紹。
接受 null case(預覽)
前面有提到 switch 關鍵字是會在編譯期間優化的,而且全部都會轉為整數,因此沒辦法接受 null 值,這次在 case 中可以加入 null 的判斷了!這個新功能本質上是一個語法糖,預先幫開發者判斷如果是 null 要做什麼,讓我們可以當成眾多 case 之一直接寫進 switch 區塊內。
EUrl eurl = null;
String type = switch(eurl) {
case null -> {
System.err.println("It's Null!!");
yield "UNKNOW";
}
case HOME -> "My Blog https://klab.tw/";
case GOOGLE, BING -> "Search Engine";
default -> {
System.err.println("Unknow Type");
yield "UNKNOW";
}
}
Pattern Matching 模式匹配(預覽)
這個功能要先提到 Java 16 的正式新功能, JEP 394: Pattern Matching for instanceof ,是讓開發者減少程式碼的語法糖,先直接看以下範例。
// Old code
if(obj instanceof Integer) {
Integer num = (Integer) obj;
System.out.printf("%d", num);
} else if(obj instanceof Double) {
Double num = (Double) obj;
System.out.printf("%f", num);
}
// Java 16 Pattern Matching
if(obj instanceof Integer num) {
System.out.printf("%d", num);
} else if(obj instanceof Double num) {
System.out.printf("%f", num);
}
可以看出新版的 code 可以簡化很多,所以 switch 關鍵字也要新增這樣的功能,也就是預覽了四代的 Pattern Matching for switch,寫起來會長這樣。
String str = switch(obj) {
case Integer n -> String.format("int %d", n);
case Double n -> String.format("double %f", n);
default -> o.toString();
}
當時有一些專案需要因應不同類型的變數做不同的回應,能早點有這些 Pattern Matching 寫起來就會輕鬆多了,而現在 Java 21 之後的開發者已經可以直接享用正式版了。
新的關鍵字 when(預覽)
這也是語法糖,如果在 switch case 比對到又要加上 if 關鍵字做更多判斷,會讓程式碼變成好多層,新的 when 關鍵字可以讓程式碼更加優雅。
// No `when` keyword
String str = switch(obj) {
case Integer n -> {
if(n >= 100){
yield String.format("int %06d", n);
} else {
yield String.format("int %03d", n);
}
}
}
// Use `when` keyword
String str = switch(obj) {
case Integer n when n >= 100 -> String.format("int %06d", n);
case Integer n -> String.format("int %03d", n);
}
如果有多個 when 分支,也要分別寫多個 Integer n,這一點在正式版的 Java 21 也維持相同設計。
四代預覽的 JEP 列表
可以上 OpenJDK 看 JEP 頁面介紹,這邊整理了四篇 JEP,從 Java 17 到 Java 20 每一代都有一個預覽功能說明。
- Java 17: JEP 406: Pattern Matching for switch (Preview) https://openjdk.org/jeps/406
- Java 18: JEP 420: Pattern Matching for switch (Second Preview) https://openjdk.org/jeps/420
- Java 19: JEP 427: Pattern Matching for switch (Third Preview) https://openjdk.org/jeps/427
- Java 20: JEP 433: Pattern Matching for switch (Fourth Preview) https://openjdk.org/jeps/433
Java 21:Pattern Matching 正式登場
Java 21 在 2023 年 9 月發布,是繼 Java 17 之後下一個 LTS 版本,也是 switch 歷史上最重要的里程碑之一。前面從 Java 17 到 Java 20 連續預覽四次的 Pattern Matching for switch,終於在這一版透過 JEP 441: Pattern Matching for switch 一次全部轉為正式版。包含 null case、型態 pattern、when 守衛條件,開發者們都可以放心寫進正式專案的程式碼中。
Java 21 還補上了一個很有意義的特性:針對 sealed 介面與 enum 的 exhaustiveness check(窮盡性檢查)。也就是說如果 switch 的選擇器是一個 sealed 介面,編譯器會檢查所有可能的子類別是否都有對應的 case,沒寫齊的話直接編譯不過,這讓 switch 搖身一變成為型別安全的 dispatch 機制。
以下範例把 null case、型態 pattern、 when 守衛、sealed 的窮盡性全部放在一起:
sealed interface Shape permits Circle, Square, Triangle {}
record Circle(double radius) implements Shape {}
record Square(double side) implements Shape {}
record Triangle(double base, double height) implements Shape {}
static double area(Shape shape) {
return switch (shape) {
case null -> 0;
case Circle c -> Math.PI * c.radius() * c.radius();
case Square s when s.side() > 0 -> s.side() * s.side();
case Square s -> 0;
case Triangle t -> t.base() * t.height() / 2;
};
}
在 Java 21 正式版中,null 與 default 也可以合併寫成 case null, default ->,如果不需要針對 null 做特殊處理會更加簡潔。另外 sealed 介面的窮盡性檢查意味著當 Shape 未來新增一個子類別時,所有沒更新的 switch 都會在編譯期被抓出來,省掉不少因為忘記處理新型別而產生的 bug。
Java 23 到 26:Primitive Types in Patterns(仍在預覽)
Java 21 之後 switch 的語法主體已經相當穩定,但 Pattern Matching 生態仍在擴張。從 Java 23 開始 OpenJDK 又端出一個以 switch 為主角的新預覽功能:Primitive Types in Patterns, instanceof, and switch。這個功能讓 switch 的 case 可以直接對原生型別(primitive type)做 pattern matching,而且 switch 的選擇器表達式也可以是 long、float、double、boolean,不再限定於整數家族。
這個功能在 Java 23 首次預覽後,接連在 Java 24、Java 25 LTS、Java 26 都維持預覽狀態,到 Java 26 已經是第四次預覽,目前還沒有確定的定版時程。先來看看寫起來長什麼樣子:
// switch 選擇器可以是 long/float/double/boolean
long code = 42L;
String size = switch (code) {
case 0L -> "zero";
case 1L, 2L, 3L -> "small";
default -> "other";
};
// case 可以用原生型別 pattern,並與 when 守衛、自動的型別轉換規則一起使用
Object obj = 42;
String label = switch (obj) {
case int i when i >= 0 -> "non-negative int " + i;
case int i -> "negative int " + i;
case long l -> "long " + l;
case double d -> "double " + d;
default -> "other";
};
這個預覽功能最有趣的地方在於它延伸了「unconditional exactness」的概念:只要兩個型別之間的轉換不會遺失資訊(例如 int 可以無條件轉為 double,反過來則可能會失去精度),編譯器就會允許這樣的 pattern matching,反之則會要求開發者額外表達意圖。每一次預覽都在這個規則上做細部調整,就是為了讓 switch 的行為與語言其它部分保持一致。Java 26 的 JEP 530 也針對 dominance check(支配關係檢查)再做了一輪收緊。
- Java 23: JEP 455: Primitive Types in Patterns, instanceof, and switch (Preview) https://openjdk.org/jeps/455
- Java 24: JEP 488 (Second Preview) https://openjdk.org/jeps/488
- Java 25: JEP 507 (Third Preview) https://openjdk.org/jeps/507
- Java 26: JEP 530 (Fourth Preview) https://openjdk.org/jeps/530
結語
從 Java 7 的 String case、Java 14 的 Switch Expressions 與 yield、Java 21 的 Pattern Matching 正式版,一路到 Java 26 仍在演進的 Primitive Types in Patterns,switch 關鍵字從一個很陽春的條件分支,逐漸變成 Java 語言中型別安全、可讀性高的 dispatch 機制。如果我們還停留在 Java 8 或 Java 11,升級到 Java 21 或最新的 Java 25 LTS 之後,會發現 switch 的新寫法讓程式碼更精簡也更安全,值得找個週末把舊專案的 switch-case 程式碼拿出來重構看看。