1. 程式人生 > >嘗鮮Java 12新特性:switch表達式

嘗鮮Java 12新特性:switch表達式

價值觀 例子 一個表 動手 expr jdk 核心 而不是 放棄

Java 12將在兩個月後(2019/3/19)發布,現已進入RDP1階段,確定加入8個JEP。其中對Java語法的改進是JEP 325: switch表達式。於是我迫不及待,提前感受一下更先進的語言特性。

因為12沒有正式發布,本文使用自己編譯的OpenJDK。嫌麻煩的話,也可以直接使用官方的ea版本。JEP325是預覽(preview)特性,編譯運行時需要添加--enable-preview參數。

顧名思義,這個feature是對switch動手腳的。包括兩個方面。

1. 簡化fall-through規則

下面這樣的switch代碼我們寫過幾萬遍了

switch (today) {
    case
SATURDAY: case SUNDAY: System.out.println("I‘m happy!"); break; case MONDAY: case TUESDAY: case WEDNESDAY: case THURSDAY: case FRIDAY: System.out.println("I‘m sad..."); break; default: System.out.println("I‘m confused."); }

這段代碼存在的問題是:

1. 內容不符合愛崗敬業的核心價值觀(敲黑板!重要!!)
2. 多個條件對應相同代碼時(比如MONDAY到FRIDAY),要重復寫多個case,冗余且醜陋
3. 每一段代碼後面都要有break,一旦忘記就會有編譯器檢測不到的邏輯錯誤
4. 變量作用域混亂

第四個問題可能長被忽略。case或者default後面是一連串的語句,而不是代碼塊(註意,它是沒有大括號的)。這種情況下定義的局部變量,其作用域不是case後的部分,而是整個switch結構。因此,下面的代碼無法通過編譯。

switch (today) {
    case MODAY:
        int x = 1;
        break
; default: int x = 0; //Variable x is already defined in the scope }

編譯器看到的是在一個作用域中存在兩個x,非常違背人類的直覺。

上面的四個問題,除了1,剩下的萬惡之源就是fall-through規則。即switch結構在找到第一個匹配的case條件後,會順序執行後面所有case對應的代碼,無論是否判斷為真。這是40多年前C語言創造後來Java原樣照抄的經典語法,但在今天看起來就顯得很呆萌了,新的語言也幾乎都放棄了fall-through。

好在,盡管後知後覺,從12開始Java開發者也可以選擇更簡潔清晰的語法了。就像這樣

switch (today) {
    case SUNDAY, SATURDAY -> System.out.println("I‘m happy!");
    case MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY -> System.out.println("I‘m happy, too!!");
    default -> System.out.println("I‘m confused.");
}

很容易看出語法的變化,這些變化也解決了上面的四個問題。歸納一下:

1. 程序內容積極向上,體現了新時代的奮鬥精神(敲黑板!重要!!)
2. 對應相同動作的多個case合並為一行,代碼更簡潔
3. 條件和動作之間用->連接,這時fall-through規則失效。匹配到的分支代碼執行完後直接跳出,不會繼續執行下面的case對應的代碼。也就是不需要再為每一個分支寫break了。程序更簡潔清晰,也更符合人類的直覺。
需要註意,為了保持向後兼容性,case條件後依然可以使用:,這時fall-through還是有效的,即不能省略原有的break。而一個switch結構裏不能混用->:,否則會有編譯錯誤。
4. 每一個->後面只允許接一個表達式、一個代碼塊、或者一個throw語句。這樣在代碼塊中定義的局部變量,其作用域就限制在代碼塊中,而不是蔓延到整個switch結構。邏輯更加清楚了。

2. switch作為表達式(expression)

switch結構一直是一個statement,而從Java 12開始,它也可以用作expression。從學院派的定義理解statement和expression的區別叫人頭疼,如果說人話的話,就是switch可以有返回值了。

作為statement的switch沒有返回值,所以我們不能寫出這樣的代碼

x = switch (y) { ... }

如果需要根據不同的條件給某個變量賦值,我們以前只能這樣做

String word = "";
switch (num) { case 1: word = "One"; break; case 2: word = "Two"; break; default: String result = String.format("Other (%d)", num); word = result; }

讓人難受的地方有兩個。

1. 重復多次地寫賦值語句,繁瑣且易錯。
2. 這段程序的終極目標是為word變量賦值,而賦值前必須在其他的地方初始化word,淡化了二者的邏輯關系,代碼也顯得瑣碎。

從12開始我們可以這樣改造代碼

String word = switch (num) {
    case 1 -> "One";
    case 2 -> "Two";
    default -> {
        String result = String.format("Other (%d)", num);
        break result;
    }
};

可見,switch成了一個表達式(expression),它有自己的返回值。每一個分支只需要決定具體的返回值是什麽,不需要考慮如何使用這個值。而全程只需要一次賦值操作。代碼整體變得更簡潔、緊湊、清晰。

而返回值又有兩種寫法。還記得嗎,上一節提到過,->後只能接三樣東西:表達式、代碼塊、throw語句。throw的情況沒有返回值,先不管它。另外兩種情況:

1. 如果分支只有一個表達式,那麽表達式本身就是switch的值,比如上面例子裏的"One""Two"
2. 如果分支是一個代碼塊,比如例子中的default,可以看到Java 12改造了break關鍵字,可以通過break result的形式返回值。switch並沒有拋棄break,而是賦予它更重要的職能。

作為expression的switch也可以使用:,在這種情況下,各個分支必須用break關鍵字返回值。像這樣

String word = switch (num) {
    case 1 : break "One";
    case 2 : break "Two";
    default : {
        String result = String.format("Other (%d)", num);
        break result;
    }
};

上面例子中,case 1case 2中的break不能省略,否則會有編譯錯誤。

很顯然,當switch用作expression時,每一個分支都必須有返回值(或者有throw異常)。我們不能寫下面這樣的代碼

String word = switch (num) {
    case 1 -> "One";
    case 2 -> "Two";
    default -> {
        System.out.println("莫挨老子");
        //錯誤: switch rule completes without providing a value
    }
};

編譯器不知道當num=3的時候應該返回什麽,於是它憤怒地拋出了一個錯誤。

最後要強調,switch在不返回值的時候,還是一個statement。而作為expression並且在一句代碼的結尾處時,不要忘了後面的分號!(親自踩坑,友情提醒)

To be continue...

可能你會覺得這些改進還是小修小改,不值得過分激動。但是,JEP 325是JEP 305: Pattern Matching的依賴。雖然沒有最終確定,但或許Pattern Matching會在不久後的幾個版本正式引入,到時又將是語言層面的大革命。後續的幾個版本還是值得期待的。

嘗鮮Java 12新特性:switch表達式