Learning Objectives
- 用 if、if-else、else if 依條件選擇要執行的程式,並寫出巢狀判斷
- 說明 Java 的條件必須是 boolean,安排 else if 條件的先後順序
- 避開省略大括號與用 == 比較字串這兩個常見陷阱
- 寫出傳統 switch,說明 break 與 fall-through 的作用
- 用箭頭 switch 與 switch 運算式(含 yield)依值選出結果
讓程式做選擇
到目前為止,程式都是由上往下一行一行全部執行。但真實的問題常常要依情況做不同的事:分數及格就印「通過」,否則印「補考」; 會員打折、非會員原價;輸入 1 是查詢、輸入 2 是存款。
條件判斷讓程式依據一個 boolean 的結果,選擇要執行哪一段程式碼。Java 有兩種寫法:
if/else:條件可以是任何 boolean 運算式,最有彈性switch:拿一個值和好幾個固定的選項比對,選項很多時比一長串 else if 好讀
本課的動畫會清楚標出哪些行被執行、哪些行被跳過,注意看執行的箭頭怎麼跳。
if 與 if-else
if (條件) {
// 條件是 true 時執行
}
if (條件) {
// 條件是 true 時執行
} else {
// 條件是 false 時執行
}
- 條件寫在小括號裡,結果必須是 boolean。Java 不會把數字當成真假:
int x = 5; if (x) { … }會編譯錯誤incompatible types: int cannot be converted to boolean,要寫成if (x != 0)。這點和 Python、C 都不同 - 不小心把
==寫成=:if (x = 5)一樣會編譯錯誤,因為x = 5的結果是 int。Java 的嚴格在這裡幫你抓到了一個常見的錯誤 - 大括號裡的程式碼形成一個區塊(block);在區塊裡宣告的變數,離開區塊就不能再使用
if 的區塊裡可以再寫 if,叫做巢狀 if(nested if):先判斷外層的條件,成立了才進去判斷內層的條件。
if、if-else 與巢狀 if
第 8 行的條件不成立,箭頭直接跳過大括號裡的程式;第 13 行的 if-else 只會執行其中一邊。第 21 行開始是巢狀 if:外層成立之後,才會判斷內層的 amount >= 1000。
else if 串
三種以上的情況,用 else if 串起來:
if (score >= 90) {
grade = "A";
} else if (score >= 80) {
grade = "B";
} else if (score >= 70) {
grade = "C";
} else {
grade = "F";
}
Java 由上往下檢查,第一個成立的條件執行它的區塊,其餘的全部跳過;全部都不成立才執行最後的 else(else 可以省略)。
所以條件的順序很重要:85 分同時符合 score >= 80 和 score >= 60,誰寫在前面就由誰決定。
範圍有重疊時,要把範圍小(條件嚴格)的放前面。另外,因為走到 else if (score >= 80) 時已經確定 score 小於 90,
不需要再寫成 score >= 80 && score < 90。
上面的 grade 沒有初始值也能編譯,因為每一條路(包含最後的 else)都有指派;如果拿掉 else,
javac 會認為 grade 可能沒有被指派,使用它時報錯 variable grade might not have been initialized。
成績等第與條件順序
上半段 85 分:第 6 行不成立、第 8 行成立,之後的 else if 一個都不會檢查。下半段把 score >= 60 放在最前面,85 分在第一個條件就成立,得到錯誤的 D。
想一想:if 與 else if
下面的程式執行結果是?
public class Main {
public static void main(String[] args) {
int count = 3;
if (count) {
System.out.println("有資料");
}
}
}
下面的程式會印出什麼?
public class Main {
public static void main(String[] args) {
int t = 25;
if (t > 10) {
System.out.println("溫暖");
} else if (t > 20) {
System.out.println("炎熱");
} else {
System.out.println("寒冷");
}
if (t > 20) {
System.out.println("開冷氣");
}
}
}
下面的程式會印出什麼?
public class Main {
public static void main(String[] args) {
int a = 5;
if (a > 10)
System.out.println("X");
System.out.println("Y");
System.out.println("Z");
}
}
兩個常見陷阱
陷阱一:省略大括號。 if 後面只有一個敘述時,Java 允許省略大括號,但這時 if 只管下一個敘述:
if (stock > 0)
System.out.println("有貨");
System.out.println("可以下單"); // 縮排看起來在 if 裡,其實一定會執行
Java 不看縮排。之後要在 if 裡多加一行時,很容易忘了補上大括號,所以一律寫大括號是比較安全的習慣。
陷阱二:用 == 比較字串。 String 是參照型別,== 比較的是「是不是同一個字串物件」,不是內容(第 08 課會說明參照)。
從鍵盤讀入、或程式算出來的字串,內容即使一樣,也可能是不同的物件,== 就會是 false。比較內容一律用 equals:
if (cmd.equals("quit")) { … } // 內容完全相同
if (cmd.equalsIgnoreCase("QUIT")) { … } // 忽略大小寫
基本型別(int、char、boolean 等)比較值時用 == 沒有問題。
大括號與字串比較
第 7 行的條件不成立,只有第 8 行被跳過,第 9 行照樣執行。後半段讀入使用者輸入的 quit,內容和 "quit" 一模一樣,== 卻是 false。
傳統 switch
拿一個值和好幾個固定的選項比對時,可以用 switch:
switch (day) {
case 1:
System.out.println("星期一");
break;
case 2:
System.out.println("星期二");
break;
default:
System.out.println("其他");
}
- 小括號裡的值可以是
int、char、String、enum 等型別(不能是double、boolean、long);每個case後面是一個常數 - Java 跳到相符的
case,從那裡開始一路往下執行,直到遇見break或 switch 結束。忘了寫break,就會繼續執行下一個 case 的程式, 叫做 fall-through(穿透)。這常常是錯誤來源,但也可以刻意利用:好幾個 case 共用同一段程式時,把它們疊在一起 - 沒有任何 case 相符時執行
default;default可以省略
break 與 fall-through
day 是 3,跳到 case 3。這個 case 沒有 break,所以會繼續執行 case 4 的程式,一直到第 16 行的 break 才離開。下半段的 case 4: case 6: case 9: case 11: 疊在一起,四個月份共用同一段程式。
箭頭 switch 與 switch 運算式
Java 14 起,switch 有了新的寫法:
箭頭 switch:用 case 值 -> 代替 case 值:,執行完箭頭右邊的程式就結束,不會穿透,也不必寫 break。
好幾個值共用同一段程式時,用逗號列出:case "latte", "mocha" ->。
switch (drink) {
case "americano" -> System.out.println("美式咖啡");
case "latte", "mocha" -> System.out.println("加牛奶的咖啡");
default -> System.out.println("其他飲料");
}
switch 運算式:switch 本身可以算出一個值,直接指派給變數(注意最後的分號):
String season = switch (month) {
case 3, 4, 5 -> "春季";
case 6, 7, 8 -> "夏季";
case 9, 10, 11 -> "秋季";
default -> "冬季";
};
- switch 運算式必須涵蓋所有可能的值,所以通常要有
default;少了 default,javac 會回報the switch expression does not cover all possible input values - 箭頭右邊需要好幾行時,寫成區塊
{ … },用yield交出這個 case 的值(yield是給 switch 運算式用的,不是return)
新寫的程式建議優先使用箭頭寫法,可以避開忘了 break 的錯誤。
箭頭 switch 與 yield
第一段只執行 "latte", "mocha" 那一個 case,執行完直接離開。第二、三段的 switch 是運算式,結果存進 season 與 price;第 26 行用 yield 交出區塊算出的值。
想一想:字串比較與 switch
下面的程式會印出什麼?
public class Main {
public static void main(String[] args) {
String s1 = "Java";
String s2 = new String("Java");
System.out.println((s1 == s2) + " " + s1.equals(s2));
}
}
下面的程式會印出什麼?
public class Main {
public static void main(String[] args) {
int n = 1;
switch (n) {
case 1:
System.out.print("A");
case 2:
System.out.print("B");
break;
case 3:
System.out.print("C");
default:
System.out.print("D");
}
System.out.println();
}
}
下面的程式會印出什麼?
public class Main {
public static void main(String[] args) {
char op = '*';
int r = switch (op) {
case '+' -> 6 + 3;
case '-' -> 6 - 3;
case '*' -> {
int t = 6 * 3;
yield t + 1;
}
default -> 0;
};
System.out.println(r);
}
}
動手寫:月份與季節
讀入一個月份。月份不在 1~12 之間時印出「月份不正確」;否則用 switch 運算式 算出季節(3~5 月春季、6~8 月夏季、9~11 月秋季、其餘冬季)。這次的輸入是 8。寫完可以把輸入改成 13、1 再試試看。
重點回顧
if (條件) { … } else { … }:條件必須是 boolean,if (x)、if (x = 5)都無法編譯- else if 串由上往下檢查,只執行第一個成立的區塊;範圍重疊時把嚴格的條件放前面
- 巢狀 if:外層成立才判斷內層
- 省略大括號時 if 只管下一個敘述,Java 不看縮排,所以一律寫大括號
- 字串比較內容用
equals(不分大小寫用equalsIgnoreCase),==比的是不是同一個物件 - 傳統 switch 從相符的 case 一路往下執行到
break(fall-through);沒有相符時執行default - 箭頭 switch(
case 值 ->)不會穿透;switch 運算式直接算出一個值,必須涵蓋所有情況,區塊裡用yield交出結果(Java 14 起)