Java語言從1995年發表至今2022年已經27個年頭,有些舊專案可能還停留在Java 6,比較近代一點的也可能停留在Java 8或Java 11這兩個LTS版。這些年間光是switch
關鍵字的功能就進化許多次,像是加入的了Switch陳述句(Switch Expressions)、箭頭回傳值、多重數值的case(Multi-Constant Case)、新的關鍵字yield
等,本文將介紹switch多了哪些方便好用的新功能。
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開始就能有包含null case的語法糖可以正式使用囉。
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
Java 17是一個LTS版,從這一代開始switch
關鍵字開始出現模式匹配功能,但在這一版的新功能是預覽階段,還不能正式使用。而且Java 17、Java 18、Java 19、Java 20連續四代都有一個switch JEP預覽,所以以下篇幅介紹的都是從Java 17開始的預覽功能,希望在Java 21能正式使用囉!
接受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寫起來就會輕鬆多了呢。
新的關鍵字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
,希望之後可以更加優雅。
更多預覽功能
可以上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