入門篇-其之九-流程控制之條件判斷

2023-11-23 18:01:05

本文中使用到的工具是Intellij IDEA和JDK 8,需要安裝兩款工具的請檢視這兩篇教學:點我檢視安裝JDK8/11/17教學點我檢視安裝Intellij IDEA教學

前面我們寫過的程式碼,都是在main方法中自上到下按順序執行的,舉一個程式碼栗子:

/**
 * 計算西瓜的價格
 *
 * @author iCode504
 * @date 2023-10-31
 */
public class MyWatermelonDemo1 {
    public static void main(String[] args) {
        int price = 2;      // 西瓜的單價
        int weight = 10;    // 西瓜的重量(公斤)
        int totalPrice = price * weight;    // 購買價格
        System.out.println("西瓜的價格是: " + totalPrice + "元");
    }
}

這段程式碼就是先定義西瓜的單價、再定義西瓜的重量,然後計算西瓜的價格,最後對價格進行輸出。像這樣程式碼從上到下執行的結構就是順序結構

程式一共有三種控制結構:順序結構、選擇結構和迴圈結構。其中選擇結構是根據條件判定的結果,選擇執行不同的程式碼,例如:紅燈停,綠燈行。判斷條件就是交通訊號燈的狀態。

Java也有選擇結構,並且有多種型別的條件判斷語句:單分支的if語句、雙分支的if-else語句、多分支的if-else if-else語句、if巢狀語句和switch語句。

一、單分支if語句

單分支if語句的語法如下:

if (條件表示式) {
    執行程式碼...
}

其中條件表示式的計算結果必須是boolean型別。如果條件表示式的計算結果是true,那麼就會執行if內部的程式碼;如果表示式為false,此時就會跳過if程式碼塊(也就是if內部程式碼不執行),概念圖如下:

我們可以在if程式碼塊中可以編寫多個執行語句。

以下是if的使用案例:

案例:之前長春下了大暴雪,氣溫驟降,我想在某寶上買幾雙棉襪子,假設每雙襪子4元,請確保輸入的數位大於0再計算購買價格。

案例:輸入一個數位,如果能被10整除,則輸出內容為:xx能被10整除。如果能被15整除,則輸出內容為:xx能被15整除。

解決本題的關鍵點在於被10整除和被15整除的條件怎麼計算。其實前面我們學過取餘運運算元,如果數值number能被10整除,那麼可以寫成number % 10 == 0;如果數值number能被15整除,那麼可以寫成number % 15 == 0。這兩個布林表示式可以寫入到兩個if語句中:

import java.util.Scanner;

/**
 * 使用if語句判斷數位能否被10和15整除
 *
 * @author iCode504
 * @date 2023-11-07
 */
public class IfDemo2 {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        System.out.println("請輸入數位: ");
        int number = scanner.nextInt();

        // 整除10的條件:數位對10取餘等於0
        if (number % 10 == 0) {
            System.out.println(number + "能被10整除");
        }

        // 整除15的條件:數位對15取餘等於0
        if (number % 15 == 0) {
            System.out.println(number + "能被15整除");
        }
    }
}

輸入不同的數位以後,會得到如下的執行結果:

案例:輸入兩個整數,如果輸入的第一個數位比第二個數位大,那麼執行兩數交換,並將交換結果輸出。否則不交換,正常輸出兩個數

本題的條件表示式是輸入的兩個數位的比較,無論數位大小比較結果如何,都需要將結果進行輸出,我們可以將輸出語句放到if語句後面執行。

兩數交換有多種方式,較為穩妥的方式是再定義一個臨時變數,用這個臨時變數來接收第一個變數值,然後第二個變數值賦值給第一個變數,最後將臨時變數的值賦值給第二個變數。

以下是範例程式碼:

import java.util.Scanner;

/**
 * 單分支if語句實現兩數交換
 *
 * @author iCode504
 * @date 2023-11-11
 */
public class IfDemo3 {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        System.out.println("請輸入兩個整數");
        int number1 = scanner.nextInt();
        int number2 = scanner.nextInt();
        int temp = 0;       // 定義臨時變數
        if (number1 > number2) {    // 實現兩數交換功能
            temp = number1;
            number1 = number2;
            number2 = temp;
        }
        System.out.println("第一個數是: " + number1 + ", 第二個數是: " + number2);
    }
}

執行結果:

當然,除了上述方式能實現兩數交換,還有其他的方式。

方式一:使用加減法進行交換(推薦使用整數,浮點數不推薦,因為浮點數計算時會出現誤差)

int number1 = 3;
int number2 = 2;
number1 = number1 + number2;	// number1 = 3 + 2 = 5
number2 = number1 - number2;	// number2 = 5 - 2 = 3
number1 = number1 - number2;	// number1 = 5 - 3 = 2

方式二:使用位運運算元進行交換(推薦使用整數,此處涉及到二進位制互斥或運算,互斥或運算可以檢視這篇文章:入門篇-其之六-Java運運算元(中)第四部分-位運運算元

int number1 = 3;
int number2 = 2;
number1 = number1 ^ number2;        // 3 ^ 2 = 1
number2 = number1 ^ number2;        // 3 ^ 1 = 2
number1 = number1 ^ number2;        // 1 ^ 2 = 3

當然,上述三種方式我個人最推薦的還是第一種定義臨時變數的方式,這種方式對處理浮點型別的數進行交換很友好,如果使用了下面兩種方式的話,可能會在計算過程中出現精度損失的問題。後兩種方式的好處是不需要定義第三個變數,只需要進行一系列運算即可完成整數值的交換。

二、if-else雙分支語句

前面講過的單分支if語句只有在布林表示式為true的時候執行其內部的內容,但是如果在布林表示式為false的時候不會做任何事情。為了解決上述問題,Java為我們提供了if-else雙分支語句。以下是雙分支if-else語句程式碼結構:

if (條件表示式) {
    執行程式碼1...
} else {
    執行程式碼2...
}

如果條件表示式的值是true,那麼就執行if內部的語句,如果條件表示式為false,此時就進入else程式碼塊。執行流程圖如下:

案例:我們還是以上述買襪子為例,最近雙十一打折,如果買了10雙及以上襪子,此時每雙襪子打八折優惠,否則打九折優惠(襪子的價格假設是4元/雙)。

題目中的條件表示式在於要買的襪子數量是否大於等於10,如果是,價格打8折,否則打9折,使用剛剛講到的if-else語句即可搞定。

當然,這道題中還有一個隱藏的細節需要我們處理:輸入襪子的數量需要大於0,否則判定為無效,這個可以使用單分支if語句就可以搞定。

以下是範例程式碼:

import java.util.Scanner;

/**
 * if-else雙分支語句
 *
 * @author iCode504
 * @date 2023-11-11
 */
public class IfDemo4 {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        System.out.println("請輸入要購買的襪子數量");
        int count = scanner.nextInt();
        double price = 4;       // 每雙襪子的價格
        double totalPrice = 0.0;    // 預設總價格為0.0
        // 需要保證輸入的襪子數量要大於0
        if (count > 0) {
            // 如果襪子的數量大於10,此時每雙襪子的價格為8折,即每雙襪子的價格乘以0.8,九折的計算方式和上述內容同理
            if (count >= 10) {
                totalPrice = price * 0.8 * count;
            } else {
                totalPrice = price * 0.9 * count;
            }
        }
        System.out.println("購買" + count + "雙襪子,雙十一期間購買價格是" + totalPrice + "元");
    }
}

執行結果(可能會出現浮點數計算不準確的情況,屬於正常現象):

三、if-else if-else多分支語句

雙分支的if-else語句對於條件表示式為truefalse的時候比較適用,但是如果對於一個問題而言,此時經過分析可能存在多個條件表示式時,if-else語句並不能很好地完成任務,此時Java為我們提供了另一種分支語句:if-else if-else語句,其語法格式如下:

if (條件表示式1) {
	執行程式碼1...
} else if (條件表示式2) {
	執行程式碼2...
} else if (條件表示式3) {
	執行程式碼3...
} ...
  else if (條件表示式n) {
	執行程式碼n...      
} else {
  	不符合上述所有條件表示式時執行else程式碼...
}

以上述語法格式為例,其執行順序為:

  • 如果條件表示式1的結果為true,那麼執行程式碼1,如果結果是false,此時就會跳轉到第一個else if
  • 如果條件表示式2的結果是true,那麼執行程式碼2,如果結果是false,那麼就會跳轉到第二個else if
  • 如果條件表示式3的結果是true,那麼執行程式碼3,如果結果是false,那麼就會跳轉到下一個else if,依次類推。
  • 當上述所有的條件表示式都不滿足(即結果全部是false)時,就會執行else中的語句。

多分支的if-else if-else語句中,你可以寫任意個else if,每個else if需要寫上條件表示式。

當然,最後的else也是可選的,ifelse-if搭配使用也是可以的。以下是執行流程圖:

案例:已知長春的地鐵/輕軌票價標準如下

  • 0-7公里(含7公里),票價2元;
  • 7-13公里(含13公里),票價3元;
  • 13-19公里(含19公里),票價4元;
  • 19-27公里(含27公里),票價5元;
  • 27-35公里(含35公里),票價6元;
  • 35公里以上每增加10公里,增加1元

假設從1號線紅嘴子地鐵站到8號線廣通路輕軌站的距離是31.4公里,從2號線汽車公園地鐵站到2號線東方廣場地鐵站的距離是20.5公里,從4號線長春站北輕軌站到4號線天工路輕軌站的距離是16.3公里。

輸入上述里程,利用程式計算出乘坐軌道交通所需要的票價。

四、if的巢狀使用

正如標題所講,if語句可以巢狀使用。舉個栗子:在main方法中,假設已經存在了一個if-else語句,那麼在這個if程式碼塊或者else程式碼塊還可以存在條件判斷語句,下面就是其中一種if的巢狀使用方式(事實上它可以if程式碼塊可以進行多種組合巢狀使用):

if (條件表示式1) {
    if (條件表示式2) {
        執行程式碼1...
    } else {
    	執行程式碼2...
    }
} else {
    if (條件表示式3) {
    	執行程式碼3...
    } else {
    	執行程式碼4...
    }
}

它的執行流程如下:

  • 如果條件表示式1的執行結果是true,進入條件表示式2,如果條件表示式2執行結果是true,此時執行程式碼1。
  • 如果條件表示式1的執行結果是true,進入條件表示式2,如果條件表示式2執行結果是false,此時執行程式碼2。
  • 如果條件表示式1的執行結果是false,進入條件表示式3,如果條件表示式3執行結果是true,此時執行程式碼3。
  • 如果條件表示式1的執行結果是false,進入條件表示式3,如果條件表示式3執行結果是false,此時執行程式碼4。

執行流程圖如下所示:

日常寫程式碼的過程中,儘量保證程式碼巢狀的層數不超過兩層。

案例:輸入三個數,要求輸出是按照從大到小進行排列。例如,輸入三個數為20、30、10,輸出結果為30、20、10

  • 第一層條件:比較第一個數和第二個數。
  • 第二層條件:比較第二個數和第三個數。
  • 第三層條件:比較第一個數和第三個數。
import java.util.Scanner;

/**
 * if的巢狀--三個數位排列
 *
 * @author iCode504
 * @date 2023-11-23
 */
public class IfDemo5 {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        System.out.print("請輸入三個整數: ");
        int number1 = scanner.nextInt();
        int number2 = scanner.nextInt();
        int number3 = scanner.nextInt();
        if (number1 > number2) {
            if (number2 > number3) {
                System.out.println("三個數從大到小的排序是: " + number1 + " " + number2 + " " + number3);
            } else {
                if (number1 > number3) {
                    System.out.println("三個數從大到小的排序是: " + number1 + " " + number3 + " " + number2);
                } else {
                    System.out.println("三個數從大到小的排序是: " + number3 + " " + number1 + " " + number2);
                }
            }
        } else {
            if (number2 < number3) {
                System.out.println("三個數從大到小的排序是: " + number3 + " " + number2 + " " + number1);
            } else {
                if (number1 > number3) {
                    System.out.println("三個數從大到小的排序是: " + number2 + " " + number3 + " " + number1);
                } else {
                    System.out.println("三個數從大到小的排序是: " + number2 + " " + number1 + " " + number3);
                }
            }
        }
    }
}

執行結果:

雖然執行結果符合我們的預期,但是我只能說:這樣的程式碼寫的非常糟糕!!!這段程式碼中if的巢狀層數達到了三層,事實上if巢狀兩層以上可讀性就大打折扣了。

我們可以使用Java陣列、迴圈和陣列方法對此問題做進一步處理(小白可以跳過這一部分)。

import java.util.Arrays;
import java.util.Scanner;

/**
 * 三數比較--按照從大到小的順序排列
 *
 * @author iCode504
 * @date 2023-11-23
 */
public class IfDemo6 {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        System.out.print("請輸入三個數位: ");
        Integer[] array = new Integer[3];
        for (int i = 0; i < array.length; i++) {
            array[i] = scanner.nextInt();
        }
        // 呼叫Arrays.sort方法對陣列排序,排序規則從大到小(lambda表示式)
        Arrays.sort(array, (o1, o2) -> o2 - o1);
        System.out.println("三個數從大到小的排序是: " + array[0] + " " + array[1] + " " + array[2]);
    }
}

執行結果:

五、switch語句

if-else if-else多分支語句可以用於多個條件表示式的判斷,我們可以寫非常多的else if,然而過多的else if可能會導致程式碼的可讀性變差。

Java為我們提供了swtich語句在一定程度上可以簡化多條件分支。以下是switch的語法結構:

switch (表示式) {
    case 值1:
        執行程式碼1...
        break;
    case 值2:
        執行程式碼2...
        // break
    case 值3:
        執行程式碼3...
        break;
    ...
    case 值n:
        執行程式碼n...
        break;
    default: 
        上述條件都不適用執行程式碼...
}

1. switch語句中表示式的計算結果、值1、值2、...、值n的資料型別必須要保持一致。支援的資料型別包括:byteshortintchar、字串型別String(JDK 7新特性)、列舉型別(後續會講到,JDK 7新特性)。

2. 如果表示式的計算結果和case中某個值相等時,就會執行這個case內的程式碼。

3. switch語句中的default是可選的,它的作用是當表示式的計算結果和所有case的值都不相等時才會執行default語句,如果default語句不存在時,所有的case對應的值和判定值都不相等時,跳出switch語句。

4. break的作用是跳出switch語句break關鍵字還會在迴圈中遇到),在每一個case對應的程式碼塊後面寫上break是個好習慣。

如果case中不加break,此時switch語句會出現穿透性,即當某一個case執行完成後,它會繼續執行下面其他的case。以下是一個是否使用break的案例:

案例:輸入數位1~7,使用switch語句輸出當前日期(假設7代表星期日)

import java.util.Scanner;

/**
 * switch語句--不加break--穿透性
 *
 * @author iCode504
 * @date 2023-11-15
 */
public class SwitchDemo1 {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        System.out.println("請輸入星期數(1~7), 7表示星期日");
        int number = scanner.nextInt();
        switch (number) {
            case 1:
                String monday = "星期一";
                System.out.println("今天是" + monday);
            case 2:
                String tuesday = "星期二";
                System.out.println("今天是" + tuesday);
            case 3:
                String wednesday = "星期三";
                System.out.println("今天是" + wednesday);
            case 4:
                String thursday = "星期四";
                System.out.println("今天是" + thursday);
            case 5:
                String friday = "星期五";
                System.out.println("今天是" + friday);
            case 6:
                String saturday = "星期六";
                System.out.println("今天是" + saturday);
            case 7:
                String sunday = "星期日";
                System.out.println("今天是" + sunday);
            default:
                System.out.println("無效日期");
        }
    }
}

執行結果:

很明顯,輸入數位3的時候,由於沒有break,當執行case 3內部程式碼以後,它會向下執行其他case中的程式碼,直至default內的程式碼執行完畢為止。並且這段程式碼還有可以進一步修改的空間,以下是加入break並進行簡化的程式碼:

import java.util.Scanner;

/**
 * switch語句--新增break--穿透性
 *
 * @author iCode504
 * @date 2023-11-15
 */
public class SwitchDemo2 {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        System.out.println("請輸入星期數(1~7), 7表示星期日");
        int number = scanner.nextInt();
        String weekday;
        switch (number) {
            case 1:
                weekday = "星期一";
                break;
            case 2:
                weekday = "星期二";
                break;
            case 3:
                weekday = "星期三";
                break;
            case 4:
                weekday = "星期四";
                break;
            case 5:
                weekday = "星期五";
                break;
            case 6:
                weekday = "星期六";
                break;
            case 7:
                weekday = "星期日";
                break;
            default:
                weekday = "無效星期";
        }
        System.out.println("今天是" + weekday);
    }
}

執行結果:

從上述結果可以看出,使用break以後,就可以阻斷switch穿透性。

switch語句執行流程如下圖所示(每個case都帶上break語句):

在瞭解了switch語句的基礎上,我們再來講解一個switch語句和if-else語句結合使用的案例:

案例:輸入年份和月份,輸出格式如下:xxxx年xx月有xx天。

常識:1、3、5、7、8、10、12恆定是31天;4、6、9、11恆定為30天。這幾個月份我們可以利用switch的穿透性替換掉多條件的else if判斷。

需要額外考慮的是:2月份的天數需要考慮年份是閏年還是平年,閏年能被400整除,例如:2000年,1600年是閏年,1900年就不是閏年。此外,如果不能被100整除,而能被4整除的也是閏年,例如:2020,2016,2004,2008年都是閏年。

結合上述分析,我們可以使用程式碼進一步復現:

import java.util.Scanner;

/**
 * switch和if結合使用
 *
 * @author iCode504
 * @date 2023-11-15
 */
public class SwitchDemo3 {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        System.out.print("請輸入年份: ");
        int year = scanner.nextInt();
        System.out.print("請輸入月份: ");
        int month = scanner.nextInt();
        int day = 0;
        switch (month) {
            // 利用switch的穿透性
            case 1:
            case 3:
            case 5:
            case 7:
            case 8:
            case 10:
            case 12:
                day = 31;
                break;
            case 4:
            case 6:
            case 9:
            case 11:
                day = 30;
                break;
            case 2:
                // 2月份需要額外針對年份進行判斷
                if (year % 400 == 0) {
                    day = 29;
                } else if (year % 4 == 0 && year % 100 != 0) {
                    day = 28;
                }
                break;
            default:
                // 不在1-12月份內做出說明
                System.out.println("無效的月份");
        }
        System.out.println(year + "年" + month + "月有" + day + "天");
    }
}

執行結果:

以我個人的開發經驗來看,使用switch語句的頻率要比使用if語句要少很多,二者的執行效率基本上差不太多。

if可以編寫更加靈活的條件表示式。比如:判斷某個整數在[10, 20]區間內,此時使用if條件表示式可以寫成if (number >= 10 && number <= 20),如果使用switch解決此問題會讓程式碼變得更加複雜(因為你要寫很多個case進行比較)。

switch更擅長特定型別的值進行比較。以上面根據某年某月求當前月份由多少天為例,事實上完全使用if語句實現,只不過我們需要寫成: