入門篇-其之八-常用類的簡單使用

2023-11-06 15:01:18

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

一、控制檯輸入類Scanner

假設今天我想在瓜攤買一個西瓜(西瓜的重量是10斤),西瓜兩塊錢一斤,此時使用Java程式程式碼如下:

/**
 * 計算西瓜的價格
 *
 * @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程式碼中實現這一功能,就需要使用到控制檯輸入類Scanner

要想執行輸入操作,需要建立一個Scanner型別的物件,Scanner類位於java.util包中(包的概念後續會講到),需要我們在類的上方手動匯入。

import java.util.Scanner;

匯入完成後,我們就可以在main方法中建立Scanner型別的物件了,在Scanner的構造器中還需要傳入一個引數System.in表示從控制檯輸入,程式碼如下:

Scanner scanner = new Scanner(System.in);

此時我們就完成了scanner物件的建立,此時我們就可以呼叫Scanner類中的方法了,由於我們定義的是int型別的變數,此時我們就可以使用Scanner類中的nextInt()方法實現輸入功能,例如:

int price = scanner.nextInt();

在控制檯輸入的內容就會賦值給當前變數並且可以參與後續的運算。

以下是解決上述方案的完整程式碼:

import java.util.Scanner;       // 要想使用Scanner類,就必須要在類的上方匯入

/**
 * 使用Scanner類實現手動輸入,然後計算結果
 *
 * @author iCode504
 * @date 2023-10-31
 */
public class MyWatermelonDemo2 {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        System.out.println("請輸入西瓜的單價: ");
        int price = scanner.nextInt();
        System.out.println("請輸入西瓜的重量(按斤計算): ");
        int weight = scanner.nextInt();
        int totalPrice = price * weight;
        System.out.println("西瓜的單價是" + price + "元, 重量是" + weight + "斤, 價格是" + totalPrice + "元");
    }
}1

執行結果:

除了booleanchar型別以外,其他七種資料型別都可以呼叫nextXxx()方法,使用方式和上述過程完全相同:

基本資料型別 呼叫方法
byte nextByte()
short nextShort()
int nextInt()
long nextLong()
float nextFloat()
double nextDouble()

除了能輸入數位以外,Scanner類還提供了字串輸入的方法:next()nextLine()。這兩個方法都能在控制檯輸入字串,二者的區別是:

  • next()方法讀取字串,直到遇到空格、製表符Tab和回車Enter為止,如果這三個符號後面還存在其他字元,next()方法都會省略。
  • nextLine()方法讀取字串,直到遇到回車Enter為止。即使當前行存在空格,也能正常輸出。

以下是兩種方法的使用案例:

import java.util.Scanner;

/**
 * next()方法和nextLine()方法的區別
 *
 * @author iCode504
 * @date 2023-10-31
 */
public class MyWatermelonDemo3 {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        System.out.println("請使用nextLine()方法輸入內容,按確認鍵結束: ");
        String strValue1 = scanner.nextLine();
        System.out.println("使用nextLine()輸出結果是: " + strValue1);

        System.out.println("請使用next()方法輸入內容,按確認鍵結束: ");
        String strValue2 = scanner.next();
        System.out.println("使用next()輸出結果是: " + strValue2);
    }
}

執行結果:

二、數學類Math

在初高中我們學習的一些數學函數在Java中同樣使用。這些數學函數都在Math類中。

2.1 絕對值、兩數的最小值和最大值

絕對值的概念:正數的絕對值是其本身,0的絕對值是0,負數的絕對值是其相反數。在Math類中,我們可以呼叫靜態方法Math.abs(number)來獲取number的絕對值,其中number的型別只能是intlongfloatdouble中的一種。

兩數的最小值可以呼叫Math.min(number1, number2),如果number1 > number2,那麼得到的結果是number2,反之,得到的結果是number1

兩數的最大值可以呼叫Math.max(number1, number2),如果number1 > number2,那麼得到的結果是number1,反之,得到的結果是number2

其中number1number2需要保證是intlongfloatdouble中的一種。

以下是範例程式碼:

/**
 * 絕對值abs()、兩數最小值min()、兩數最大值max()的應用
 *
 * @author iCode504
 * @date 2023-10-31
 */
public class MathDemo1 {
    public static void main(String[] args) {
        // 取絕對值
        int intValue1 = 20;
        int result1 = Math.abs(intValue1);
        double doubleValue1 = -2.45;
        double result2 = Math.abs(doubleValue1);
        float floatValue1 = 0.0f;
        float result3 = Math.abs(floatValue1);

        System.out.println("----------取絕對值----------");
        System.out.println("result1 = " + result1);
        System.out.println("result2 = " + result2);
        System.out.println("result3 = " + result3);

        // 兩數取最小值、最大值
        int intValue2 = 30;
        int intValue3 = 40;
        int result4 = Math.min(intValue2, intValue3);
        int result5 = Math.max(intValue2, intValue3);
        System.out.println("----------取最小值、最大值----------");
        System.out.println("result4 = " + result4);
        System.out.println("result5 = " + result5);
    }
}

執行結果:

那麼Math.abs()Math.min()Math.max()為什麼只支援intlongfloatdouble四種型別。我們使用Ctrl和滑鼠左鍵點選abs()方法進入原始碼:

此時按Alt7鍵,會列舉出當前類所有的方法,此時我們在列表中直接輸入abs搜尋,發現只有四個結果:

此時我們依次點選進入檢視原始碼,發現它們支援的資料型別只有intlongfloatdouble。以int型別的abs(int)方法為例,我們發現方法內部就是一個三元運運算元組成的表示式:

public static int abs(int a) {
    return (a < 0) ? -a : a;
}

如果a < 0,那麼得到的結果就是其相反數-a,反之,0和正數得到的絕對值就是其本身。longfloatdoubleabs()方法亦同理。

此時我們可以按照上述的方式找到minmax方法,發現二者也是僅支援intlongfloatdouble,方法列表如下:

min(int, int)方法為例,此時我們點選檢視原始碼,發現這個方法體中也用到了三元表示式:

public static int min(int a, int b) {
    return (a <= b) ? a : b;
}

如果a小於等於b,那麼最小值就是a,反之為b

而浮點型別的min(double, double)方法原始碼則在此基礎上做了進一步判斷:

public static double min(double a, double b) {
    if (a != a)
        return a;   // a is NaN
    if ((a == 0.0d) &&
        (b == 0.0d) &&
        (Double.doubleToRawLongBits(b) == negativeZeroDoubleBits)) {
        // Raw conversion ok since NaN can't map to -0.0.
        return b;
    }
    return (a <= b) ? a : b;
}

如果引數a的值是NaN(NaN是一個特殊的浮點型別的數值,表示無效或者無意義的數值結果,例如:0.0 / 0.0得到的結果沒有意義,其結果就是NaN),由於NaN是無意義的結果,因此兩個NaN的值比較結果就是false。原始碼中的第一個if判斷就是針對NaN結果的判斷,如果a的確是NaN,那麼比較的結果沒有意義,返回的結果也就是變數a本身的值NaN。

第二個比較主要是針對a的值是0.0,b的值是-0.0的情況,0.0在預設情況下無論前面加上正負號都是0.0,第三個條件中Double.doubleToRawLongBits()方法是將當前按浮點數轉換成64位元的long型別數,negativeZeroDoubleBits就是上述方法預設的-0.0轉換成long型別的數位,如果此時Double.doubleToRawLongBits(b)得到的結果和negativeZeroDoubleBits的值完全相同,那麼得到的結果是b的值-0.0。

如果上述兩個條件都不符合,那麼就使用三元運運算元進行比較,如果a小於等於b,返回值是a,反之為b

2.2 數學常數\(\pi\)\(e\)

數學常數是指在數學領域中經常使用的,具有特定數值的量。在中學階段,我們接觸到的兩個常數是圓周率\(\pi\)(3.1415926...)和自然對數\(e\)(2.7182818...)。這兩個常數在Java的Math類有儲存,我們只需要呼叫Math.PI即可獲取\(\pi\)值,呼叫Math.E即可獲取\(e\)值。

/**
 * 數學常數:圓周率和自然對數
 *
 * @author iCode504
 * @date 2023-11-02
 */
public class MathDemo2 {
    public static void main(String[] args) {
        System.out.println("圓周率的值是: " + Math.PI);
        System.out.println("自然對數的值是: " + Math.E);
    }
}

執行結果:

從執行結果中我們可以發現,Math.PIMath.E只輸出了小數點後的一部分,這是因為在Math類中關於PIE使用的是double型別,由於double的精度只有15位,因此輸出結果保留了小數點後15位。

2.3 三角函數

Math類中定義了很多和三角函數相關方法,所有的三角函數得到的結果都是double型別,這裡選擇了3個具有代表性的三角函數:

方法 說明
sin(a) 正弦函數
cos(a) 餘弦函數
tan(a) 正切函數

和數學上的使用基本上一樣,我們只需要確定a的值即可。例如:\(sin(\frac{\pi}{6})=0.5,cos(\frac{\pi}{3}=0.5),tan(\frac{\pi}{4})=1\),此時我們可以使用程式來檢驗一下:

/**
 * 三角函數的使用
 *
 * @author iCode504
 * @date 2023-11-02
 */
public class MathDemo3 {
    public static void main(String[] args) {
        // 弧度使用Math.PI來表示
        double sinResult = Math.sin(Math.PI / 6);
        double cosResult = Math.cos(Math.PI / 3);
        double tanResult = Math.tan(Math.PI / 4);

        System.out.println("sinResult = " + sinResult);
        System.out.println("cosResult = " + cosResult);
        System.out.println("tanResult = " + tanResult);
    }
}

執行結果:

但是從執行結果中我們可以發現得到的結果和預期的值相差「一點點」,出現上述情況的原因主要有兩點:首先,計算機本身處理浮點型別的數值就不準確。另外,Math.PI的值是小數點的後15位,做不到十分精確。因此得到的結果和期望值存在誤差。

2.4 指數函數和對數函數

Math類中定義瞭如下常用的指數函數和對數函數:

方法名 說明
sprt(a) 求a的平方根
pow(a, b) 求a的b次方,即\(a^b\)
exp(a) 求自然對數\(e\)的a次方,即\(e^a\)
log(a) 求以自然對數\(e\)為底,a的對數,即\(ln(a)\)
log10(a) 求以10為底,a的對數,即\(log_{10}a\)

以下是這些數學函數在程式碼中的應用:

/**
 * 指數函數、對數函數的使用
 *
 * @author iCode504
 * @date 2023-11-03
 */
public class MathDemo4 {
    public static void main(String[] args) {
        int number1 = 49;
        double result1 = Math.sqrt(number1);    // 求number1的平方根
        int number2 = 3;
        int number3 = 4;
        double result2 = Math.pow(number2, number3);    // 求number2的number3次方
        double result3 = Math.exp(3);       // 求e的3次方
        double result4 = Math.log(2 * Math.E);      // 求以e為底,2e的對數
        double result5 = Math.log10(100);       // 求以10為底,100的對數

        System.out.println("result1 = " + result1);
        System.out.println("result2 = " + result2);
        System.out.println("result3 = " + result3);
        System.out.println("result4 = " + result4);
        System.out.println("result5 = " + result5);
    }
}

執行結果:

2.5 數位的舍入操作

在進行數學運算時,我們可能需要對小數進行舍入操作(例如:四捨五入),Math類為我們提供了以下四種關於小數舍入的方法:

方法名 返回型別 說明
ceil(x) double 獲取大於或等於當前數值的最小整數
floor(x) double 獲取小於或等於當前數值最大整數
rint(x) double 獲取當前數值最接近的整數,如果有兩個相同接近的整數,取偶數
round(x) double 四捨五入,舍入數位以第一位小數為基準

以下是這些數學函數在程式碼中的應用:

/**
 * 舍入函數的使用
 *
 * @author iCode504
 * @date 2023-11-03
 */
public class MathDemo5 {
    public static void main(String[] args) {
        double number1 = 2.46;
        double number2 = -2.34;

        // 獲取大於或等於當前數值的最小整數
        double result1 = Math.ceil(number1);
        double result2 = Math.ceil(number2);
        System.out.println("result1 = " + result1);
        System.out.println("result2 = " + result2);

        System.out.println("--------------------");
        // 獲取小於或等於當前數值最大整數
        double result3 = Math.floor(number1);
        double result4 = Math.floor(number2);
        System.out.println("result3 = " + result3);
        System.out.println("result4 = " + result4);

        System.out.println("--------------------");
        // 獲取當前數值最接近的整數,如果有兩個相同接近的整數,取偶數
        double result5 = Math.rint(number1);
        double result6 = Math.rint(number2);
        double result7 = Math.rint(5.5);
        System.out.println("result5 = " + result5);
        System.out.println("result6 = " + result6);
        System.out.println("result7 = " + result7);

        System.out.println("--------------------");
        // 四捨五入,舍入數位以第一位小數為基準
        double result8 = Math.floor(number1);
        double result9 = Math.floor(number2);
        System.out.println("result7 = " + result8);
        System.out.println("result8 = " + result9);
    }
}

執行結果:

2.6 亂數

Math類中為我們提供了一個獲取亂數的方法random(),它預設在\([0,1)\)範圍內生成小數。我們可以利用這個範圍,生成任意範圍的數位。

例如:利用Math.random()所給的範圍,生成\([15, 60]\)之間的亂數。

首先,整數範圍\([15, 60]\)可以等價寫成\([15, 61)\)

再獲取範圍差:\(61 - 15 = 46\)

利用不等式的性質,將原有的\([0, 1)\)乘以46得到\([0, 46)\),再將現有的範圍再加上15,即可獲得目標範圍:\([15, 61)\)

總結:從\([0,1)\)轉換到\([15,61)\)先乘以46,再加15即可。

以下上述案例在Java程式碼中的實現:

/**
 * Math.random()生成亂數
 *
 * @author iCode504
 * @date 2023-11-03
 */
public class MathDemo6 {
    public static void main(String[] args) {
        // 每次生成的亂數值都不相同
        double result1 = Math.random();
        double result2 = Math.random();
        double result3 = Math.random();

        System.out.println("result1 = " + result1);
        System.out.println("result2 = " + result2);
        System.out.println("result3 = " + result3);
        System.out.println("--------------------");

        // 由於生成的是[15,60]之間的整數,需要將計算結果強制轉換int型別
        int randomNumber1 = (int) (Math.random() * 46 + 15);
        int randomNumber2 = (int) (Math.random() * 46 + 15);
        int randomNumber3 = (int) (Math.random() * 46 + 15);

        System.out.println("randomNumber1 = " + randomNumber1);
        System.out.println("randomNumber2 = " + randomNumber2);
        System.out.println("randomNumber3 = " + randomNumber3);
    }
}

每次得到的結果都不相同:

三、亂數類Random

前面我們學過Math.random()方法來生成亂數,但是這個方法存在一個侷限是它預設生成的範圍是\([0,1)\)之間的浮點數值,如果需要更大範圍的亂數需要進行一定的計算並且需要進行強制型別轉換,可能會導致程式碼可讀性變低。

而接下來要提到的Random類可以避免強制型別轉換的問題,並且包含Math.random()方法所不包含的一些特性。

3.1 亂數相關的概念

偽亂數:偽亂數是計算機利用特定的演演算法計算出來的\([0,1)\)均勻分佈的隨機序列。雖然偽亂數並不是真正的亂數,但是它們具有類似亂數的統計特徵:均勻性和獨立性。在計算偽亂數時,如果使用的初始值(也稱作亂數種子)不變,那麼生成偽亂數的序列也不會改變。偽亂數可以使用程式大量生成。

亂數種子:亂數種子是在偽亂數生成器中用於生成偽亂數的初始數值,亂數種子一般是數位。在偽亂數生成器中,給定相同的種子值,將會生成相同的偽亂數的序列。

3.2 亂數類Random的使用

和前面講過的Scanner類一樣,Random類也在java.util包中。建立亂數的方法如下:

1. 在類的上方匯入Random類:

import java.util.Random;

2. 建立一個Random物件:有兩種方式:一種是給定亂數,另外一種就是不給亂數:

構造方法 說明
Random(long) 傳入一個long型別的亂數種子,後續生成一個固定的亂數序列
Random() 如果構造方法中沒有亂數,計算機會給定一個亂數種子。當然,後續生成的亂數列就不是固定的
Random random1 = new Random(20);		// 給定一個亂數種子20,後續會生成一個固定的亂數數列
Random random2 = new Random();			// 不直接給定亂數種子,讓計算機自己分配一個種子,生成一個不固定的亂數數列

3. 根據要生成的亂數型別,呼叫亂數方法,支援整數型別(intlong)、浮點型(floatdouble)和布林型別(boolean)。方法列表如下:

方法名稱 說明
nextInt() 生成int範圍內的亂數
nextInt(int) 生成1到int最大值範圍內(不包含int最大值)的亂數
nextLong() 生成long範圍內的亂數
nextFloat() 生成float範圍內的亂數
nextDouble() 生成double範圍內的亂數
nextBoolean() 隨機生成truefalse

以下是上述方法的使用:

import java.util.Random;

/**
 * 亂數的應用
 *
 * @author iCode504
 * @date 2023-11-04
 */
public class RandomDemo1 {
    public static void main(String[] args) {
        // 不使用亂數種子
        Random random1 = new Random();
        int result1 = random1.nextInt();            // 生成int範圍內的隨機整數
        int result2 = random1.nextInt(60);      // 生成1到60範圍內的隨機整數
        long result3 = random1.nextLong();          // 生成long範圍內的隨機整數
        float result4 = random1.nextFloat();        // 生成float範圍內的隨機整數
        double result5 = random1.nextDouble();      // 生成double範圍內的隨機整數
        boolean result6 = random1.nextBoolean();    // 生成true或false

        System.out.println("result1 = " + result1);
        System.out.println("result2 = " + result2);
        System.out.println("result3 = " + result3);
        System.out.println("result4 = " + result4);
        System.out.println("result5 = " + result5);
        System.out.println("result6 = " + result6);

        System.out.println("--------------------");
        // 使用亂數種子生成固定序列
        Random random2 = new Random(20);
        for (int i = 0; i < 5; i++) {
            int randomValue = random2.nextInt();
            System.out.println("randomValue" + (i + 1) + " = " + randomValue);
        }
    }
}

執行結果:

我們也可以使用Random解決上述生成亂數問題:

import java.util.Random;

/**
 * 使用Random類生成[15, 60]範圍內的整數
 *
 * @author iCode504
 * @date 2023-11-04
 */
public class RandomDemo2 {
    public static void main(String[] args) {
        Random random = new Random();   // 不設定隨機種子
        int result = random.nextInt(46) + 15;
        System.out.println("result = " + result);
    }
}

多執行幾次程式,我們發現生成的亂數確實在\([15,60]\)範圍內:

以上是使用Random類生成亂數,相對於Math.random()而言,生成亂數可以省去強制型別轉換,相對方便了一些。

Random類是一個方便實用的工具類,它提供了各種方法來獲取不同型別和範圍的亂數,適用於各種模擬、遊戲、密碼學等方面應用。通過使用Random類,開發人員可以輕鬆地生成具有良好隨機性和不可預測性的偽亂數,從而提高應用程式的靈活性和效率。