使用標準 API 類別


Java SE提供了標準API,這些API就是由許多類別所組成,你可以直接取用這些標準類別,省去你撰寫程式時重新打造輪子的需求。底下來舉兩個基本的標準類別:java.util.Scannerjava.math.BigDecimal

使用java.util.Scanner

舉例來說,目前為止的程式範例都很無聊,變數值都是寫死的,沒有辦法接受使用者的輸入。如果要在文字模式下取得使用者輸入,基本上可以使用System.in物件上的read()方法,不過這個方法是以int型態傳回讀入的字元編碼。想想,如果你輸入了一個'9'字元,使用System.in.read()的話,就還得自己將'9'字元轉換為整數9,當然是不方便,實際上,System.in.read()這類的方法,是作為底層API使用,開發者會在上頭依需求施予更高層次的封裝。

java.util.Scanner就是這類的封裝,底下直接以實際範例來說明:

package cc.openhome;

import java.util.Scanner;

public class Guess {
    public static void main(String[] args) {
        Scanner userInput = new Scanner(System.in);
        int number = (int) (Math.random() * 10);
        int guess;
        
        do {
            System.out.print("猜數字(0 ~ 9):");
            guess = userInput.nextInt();
        } while(guess != number);
        
        System.out.println("猜中了...XD");
    }
}

由於你不想每次都鍵入java.util.Scanner,所以一開始就使用import告訴編譯器,如此之後就只要鍵入Scanner就可以了。在建立Scanner實例時,必須傳入java.io.InputStream的實例,之後介紹到輸入輸出串流時會知道,System.in就是一種InputStream,所以在這邊可以在建構Scanner實例時使用。


接下來你想要什麼資料,就跟Scanner物件要就可以了,正如其名,Scanner實例會幫你掃描標準輸入,看看使用者有無輸入字元,怎麼掃描你就不用管了,反正一定是有個Scanner.java定義了程式碼作這些事,編譯為Scanner.class並放在rt.jar中供大家使用。

ScannernextInt()方法會看看標準輸入中,有沒有輸入下一個字串(以空白或換行為區隔),有的話會嘗試將之剖析為int型態,Scanner對每個基本型態,都會有個對應的nextXXX()方法,例如nextByte()、nextShort()、nextLong()、nextFloat()、nextDouble()、nextBoolean()等,如果直接取得上一個字串(以空白或換行為區隔),則使用next(),如果想取得使用者輸入的整行文字,則使用nextLine()(以換行為區隔)。

慣例上,套件名稱為java開頭的類別,表示標準類別。


使用java.math.BigDecimal

知道在 Java 中執行1.0 – 0.8的結果是多少嗎?答案不是0.2,而是0.19999999999999996!為什麼?這是Java的臭蟲(Bug)嗎?不!不是的!你使用別的程式語言(例如JavaScript、Python等)也有可能是顯示這個結果。


簡單來說,Java(包括其它程式語言)遵合IEEE 754浮點數演算(Floating-point arithmetic)規範,使用分數與指數來表示浮點數。例如0.5會使用1/2來表示,0.75會使用1/2 + 1/4來表示,0.875會使用1/2 + 1/4 + 1/8來表示,而0.1會使用1/16 + 1/32 + 1/256 + 1/512 +1/4096 + 1/8192 + ...無限循環下去,無法精確表示,因而造成運算上的誤差。

再來舉個例子,你覺得以下程式片段會顯示什麼結果?

double a = 0.1;
double b = 0.1;
double c = 0.1;
if((a + b + c) == 0.3) {
    System.out.println("等於 0.3");
}
else {
    System.out.println("不等於 0.3");
}

由於浮點數誤差的關係,結果是顯示「不等於0.3」!類似的例子還很多,結論就是,如果要求精確度,那就要小心使用浮點數,而且別用==直接比較浮點數運算結果


那麼要怎麼辦得到更好的精確度?可以使用java.math.BigDecimal類別,以方才的1.0 – 0.8為例,如何得到0.2的結果?直接使用程式來示範:

package cc.openhome;

import java.math.BigDecimal;

public class DecimalDemo {
    public static void main(String[] args) {
        BigDecimal operand1 = new BigDecimal("1.0");
        BigDecimal operand2 = new BigDecimal("0.8");
        BigDecimal result = operand1.subtract(operand2);
        System.out.println(result);
    }
}

建構BigDecimal的方法之一是使用字串,BigDecmial在建構時會剖析傳入字串,以預設精度進行接下來的運算,BigDecimal提供有plus()、substract()、multiply()、divide()等方法,可以進行加、減、乘、除等運算,這些方法都會傳回代表運算結果的BigDecimal

上面這個範例可以顯示出0.2的結果,再來看利用BigDecimal比較相等的例子:

package cc.openhome;

import java.math.BigDecimal;

public class DecimalDemo2 {
    public static void main(String[] args) {
        BigDecimal op1 = new BigDecimal("0.1");
        BigDecimal op2 = new BigDecimal("0.1");
        BigDecimal op3 = new BigDecimal("0.1");
        BigDecimal result = new BigDecimal("0.3");
        if(op1.add(op2).add(op3).equals(result)) {
            System.out.println("等於 0.3");
        }
        else {
            System.out.println("不等於 0.3");
        }
    }
} 

由於BigDecimaladd()等方法都會傳回代表運算結果的BigDecmial,所以就直接利用傳回的BigDecimal再呼叫add()方法,最後再呼叫equals()比較兩個BigDecimal實質上是否相同,所以有了op1.add(op2).add(op3).equals(result)的寫法。

你可以在 JWorld@TW 的  [FAQ] 為何 1.0 - 0.8 不是 0.2? 討論中,看到更多浮點數誤差的例子。