# 語言基礎 ###### tags: `java` ## 目錄 - [註解](#註解) - [標準輸出與輸入](#標準輸出與輸入) - [標準輸出](#標準輸出) - [標準輸入](#標準輸入) - [標準錯誤輸出](#標準錯誤輸出) - [資料](#資料) - [資料型態](#資料型態) - [變數](#變數) - [常數](#常數) - [運算](運算) - [算術運算](#算術運算) - [比較運算](#比較運算) - [條件運算](#條件運算) - [邏輯運算](#邏輯運算) - [位元運算](#位元運算) - [遞增、遞減運算](#遞增、遞減運算) - [指派運算](#指派運算) - [運算子優先權](#運算子優先權) ### 註解 註解可以用來在程式碼裡面加上自己的筆記、程式碼說明、紀錄或一些程式中一些要注意的事項,程式語言用來與電腦溝通,而註解的作用則是是用來與自己或其他開發人員溝通。 被包圍在註解內的文字,編譯器不會去處理,也不會降低程式執行效能,所以註解文字中撰寫任何的東西,對編譯出來的程式不會有任何影響。 在 Java 中可以用兩種註解方式: - 單航註解 - 區塊註解 例如: ``` /* * 開發者:Aaron Ho * 版本:0.01 * 日期:2021-07-30 */ public class HelloWorld { public static void main(String[] args) throws Exception { // 下面程式碼輸出Hello, World!字串到畫面上 System.out.println("Hello, World!""); } } ``` `/*` 與`*/` 為區塊註解,以 `/*` 開始註解文字,以 `*/` 結束註解文字,用來一次包圍多行的註解文字,有時讓註解文字看來比較整齊,會多使用一些 `*`來排版,請記得不能用巢狀方式來撰寫多行註解,例如為錯誤註解方式: ``` /* 註解文字1~~~ /* 註解文字2~~~ */ */ ``` > 補充: > > 編譯器在處理註解時,會以倒數第二個 `*/` 來當成結束,所以對最後一個 `*/` 就會認為是錯誤語法,這時就會出現編譯錯誤的訊息。 `//` 為單行註解,在 `//` 之後的該行文字都被視為註解文字,多行註解可以包括單行註解,例如: ``` /* 註解文字1~~~ // 註解文字2~~~ */ ``` > 補充: > > 程式碼內隨時都可以插入註解文字,註解的目的在於說明程式,是給開發人員看的,當程式碼改變造成邏輯不同時,如果有註解,請記得更新,雖然不更新註解不會造成執行上的問題,卻會對下一位看到的開發者造成理解上的困難。 註解的另一個作用則是用來暫時不執行某些程式碼,在除錯時常常會利用註解來把程式碼標注起來,讓編譯器可以跳過執行來確認程式碼是否有問題,例如: ``` public class App { public static void main(String[] args) throws Exception { // System.out.println("Hello, World!"); } } ``` 執行後不會有任何文字被輸出到畫面上。 ## 標準輸出與輸入 ### 標準輸出 System.out用來將文字輸出到標準輸出上,我們可以用來將程式執行當中或執行完畢做一個輸出,讓使用者來檢視程式執行後的結果。‘ System.out提供了幾種常用的print系列方法可以用來做輸出: | 方法名稱 | 描述 | | --------- | ------------ | | println() | 輸出並換行 | | print() | 輸出,不換行 | | printf() | 格式化輸出 | 例如下面範例: ``` public class App { public static void main(String[] args) { System.out.println("Hello, World!"); System.out.print("Hello, "); System.out.print("Aaron!"); System.out.printf("My name is %s", "Aaron"); } } ``` 輸出如下: ``` Hello, World! Hello, Aaron!My name is Aaron ``` 而程式: ``` public class App { public static void main(String[] args) throws Exception { System.out.println("Hello, World!"); System.out.print("Hello, "); System.out.print("Aaron!\n"); System.out.printf("My name is %s", "Aaron"); } } ``` 輸出如下: ``` Hello, World! Hello, Aaron! My name is Aaron ``` 加上了`\n`可以用來輸出換行,而%s代表後面會用字串來取代,如果是%d則代表用數字來取代,例如: ``` public class App { public static void main(String[] args) throws Exception { System.out.printf("My name is %s, I'm %d years old", "Aaron", 18); } } ``` `%s` 對應至 "Aaron",而 `%d` 對應至數字 18,所以程式輸出如下: ``` My name is Aaron, I'm 18 years old ``` ### 標準輸入 一般來說很少直接使用它,因為 System.in 物件所提供的 read() 方法,是從輸入串流取得一個位元組的資料,並傳回該位元組的整數值,但通常在實務上要取得的使用者輸入會是一個字串,或是數字等等,所以 System.in 物件的 read() 方法一次只讀入一個位元組資料的方式並不容易使用,所以很少直接使用。 ### BufferedReader 比較常用來接受使用者輸入的會是透過 BufferedReader 來取得輸入,其使用方式要完全理解較困難,但因為使用方法都是固定的,所以每次使用時直接複製貼上即可,就當作是語法規則就好。 透過呼叫BufferedReader 物件的 readLine() 方法可以用來讀取使用者輸入的字串,請看下面範例: ``` import java.io.*; public class App { public static void main(String[] args) throws Exception { BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(System.in)); System.out.print("請輸入文字: "); String text = bufferedReader.readLine(); System.out.println("您的輸入為: " + text); } } ``` **解說** ``` import java.io.*; ``` BufferedReader 類別是 java.io 套件中所提供的一個類別,所以使用這個類別時必須使用 import 告訴編譯器這個類別位於 java.io 套件下。 ``` BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(System.in)); ``` 1. BufferedReader bufferedReader 表示宣告一個型態為 BufferedReader 的參考名稱。 2. new BufferedReader() 表示透過BufferedReader 類別來建立一個BufferedReader物件。 3. new InputStreamReader(System.in) 表示以 System.in 物件來建立一個 InputStreamReader 物件。 也就是要透過BufferedReader來讀取標準串流資料的話,必須先建立BufferedReader物件,而BufferedReader物件在建立時需要InputStreamReader物件,而 InputStreamReader建立時需要System.in這個標準輸入物件,透過這三個物件的組合,就可以進行文字輸入的讀取。 ``` String text = bufferedReader.readLine(); ``` readLine() 方法會傳回使用者在按下 Enter 鍵之前的所有字元輸入並存入text這個變數內,不包括最後按下的 Enter 返回字元。 執行後輸出如下: ``` 請輸入文字: Aaron Ho 您的輸入為: Aaron Ho ``` > **補充** > > BufferedReader必須處理 java.io.IOException 例外(exception),例外處理機制是 Java 提供給程式設計人員捕捉程式中可能發生的錯誤所提供的機制,main() 方法後面的`throws Exception`會直接捕捉並忽略所有例外,因此在這裡不需要再做額外的處理。 標準輸入的資料一定都是文字,如果需要需入數字,則可以把輸入的資料透過Integer.parseXXX()方法做轉換,例如: ``` import java.io.*; public class App { public static void main(String[] args) throws Exception { BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(System.in)); System.out.print("請輸入第一個數字: "); String text1 = bufferedReader.readLine(); int num1 = Integer.parseInt(text1); System.out.print("請輸入第二個數字: "); String text2 = bufferedReader.readLine(); int num2 = Integer.parseInt(text2); System.out.printf("總合為: %d", num1 + num2); } } ``` Integer.parseInt()方法轉換如果成功,會得到轉換後的數字,失敗會輸線例外狀況。 #### 練習 ##### 寫一程式,接受兩個標準輸入,最後將兩個輸入串接再一起輸出到標準輸出。 ``` import java.io.BufferedReader; import java.io.InputStreamReader; public class App { public static void main(String[] args) throws Exception { BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(System.in)); System.out.print("請輸入第一串文字: "); String text1 = bufferedReader.readLine(); System.out.print("請輸入第二串文字: "); String text2 = bufferedReader.readLine(); System.out.printf("您的輸入為: %s%s", text1, text2); } } ``` ### 標準錯誤輸出 除了System.out可以輸出資料之外,還有一個錯誤輸出串流,也可以用來輸出訊息,通常用來輸出程式內發生的錯誤訊息。 ``` public class App { public static void main(String[] args) throws Exception { System.out.println("I'm out"); System.err.println("I'm err"); } } ``` 程式執行結果如下: ``` I'm out I'm err ``` 光看輸出結果無法分辨是透過哪一個標準串流物件輸出的資料,可以使用指令執行的方式做個實驗如下: ``` java App > null I'm err ``` `>`符號為標準輸出的重新導向,`null`為空目的地的意思,任何資料導向null都相當於拋棄,執行後會發現,輸出只剩下一行,這是因為另一行透過System.out輸出的資料會重新導向到null目的地去了,所以不會出現在螢幕上。 也可以將輸出導向一個檔案,例如: ``` java App > App.txt I'm err ``` 透過任何編輯器打開文字檔,會發現檔案內容如下: ``` I'm out ``` > **補充** > > 重新導向輸出至指定的目的除了使用 `>`之外也可以使用 `>>`,後者除了重導標準輸出之外,還有附加(Append)的功能,也就是會把輸出的內容附加到目的檔案的後頭,如果目的檔案本來不存在就會先建立一個再進行輸出。 ### 輸出格式控制 標準輸出通常是使用文字模式作為輸出,這邊我介紹幾個輸出格式控制技巧,在文字模式顯示時可以協助控制輸出的格式,首先介紹格式控制字元,先使用表格列出一些常用的控制字元: | 控制字元 | 作用 | | -------- | --------------------------------- | | \\ | 反斜線 | | ' | 單引號' | | " | 雙引號" | | \uxxxx | 以 16 進位數指定 Unicode 字元輸出 | | \xxx | 以 8 進位數指定 Unicode 字元輸出 | | \b | 倒退一個字元 | | \f | 換頁 | | \n | 換行 | | \r | 游標移至行首 | | \t | 跳格(一個Tab鍵) | 使用Unicode 字元編碼來輸出 「Hello」字串: ``` public class App { public static void main(String[] args) throws Exception { System.out.println("\u0048\u0065\u006C\u006C\u006F"); } } ``` > 補充: > > Unicode字碼表:https://www.ifreesite.com/unicode/character.htm 在輸出數值時,預設都會以十進位的方式來顯示數值,要輸出不同進位的數字可以透過 java.lang.Integer 所提供的各種 toXXX() 方法來達成,例如: ``` public class App { public static void main(String[] args) throws Exception { // 十進位轉成二進位 System.out.println(Integer.toBinaryString(100)); // 十進位轉成十六進位 System.out.println(Integer.toHexString(100)); // 十進位轉成八進位 System.out.println(Integer.toOctalString(100)); } } ``` 也可以使用System.out的printf()方法來達成: ``` public class TigerNumberDemo { public static void main(String[] args) throws Exception { // 輸出以十進位表示 System.out.printf("%d%n", 100); // 輸出以十六進位表示 System.out.printf("%x%n", 100); // 輸出以八進位表示 System.out.printf("%o%n", 100); } } ``` - '%d' 表示將指定的數值以十進位表示 - '%o' 是八進位表示 - '%x' 是十六進位表示 - '%n' 是指輸出平台特定的換行字元,在 Windows 下為 'r\n',MacOS/Linux 下則為 '\n'。 > **補充** > > printf()方法無法輸出二進位數字。 ##### 常用轉換符號 | 格式字元 | 作用 | | -------- | ------------------------------------------------------------ | | %% | 在字串中顯示 % | | %d | 以 10 進位整數方式輸出,提供的數必須是 Byte、Short、 Integer、Long 或 BigInteger | | %f | 將浮點數以 10 進位方式輸出,提供的數必須是 Float、Double 或 BigDecimal | | %e, %E | 將浮點數以 10 進位方式輸出,並使用科學記號,提供的數必須是 Float、 Double 或 BigDecimal | | %a, %A | 使用科學記號輸出浮點數,以 16 進位輸出整數部份,以 10 進位輸出指數部份,提供的數必須是 Float、Double、BigDecimal | | %o | 以 8 進位整數方式輸出,提供的數必須是 Byte、Short、 Integer、Long 或 BigInteger | | %x, %X | 將浮點數以 16 進位方式輸出,提供的數必須是 Byte、Short、 Integer、Long 或 BigInteger | | %s, %S | 將字串格式化輸出 | | %c, %C | 以字元方式輸出,提供的數必須是 Byte、Short、Character 或 Integer | | %b, %B | 將 "true" 或 "false" 輸出(或 "TRUE"、"FALSE",使用 %B)。另外,非 null 值輸出是 "true",null 值輸出是 "false" | 輸出浮點數時可以指定小數位數,例如: ``` System.out.printf("%.2f", 100.234); ``` 會輸出: ``` 100.23 ``` 也可以指定輸出時,至少要預留的字元寬度,例如: ``` System.out.printf("%10.2f", 100.234); ``` 由於預留了 10 個字元寬度,不足的部份要由空白字元補上,所以執行結果會輸出如下(100.234 只佔6個字元,所以補上4個空白在前端): ``` 100.234 ``` ## 資料 ### 資料型態 程式在執行的過程中,需要運算許多的資料,也需要儲存許多的資料,這些資訊可能是由使用者輸入、資料庫、檔案或網路上取得,在程式執行過程中,這些資料會透過叫「變數」(Variable)的東西來存放,以便程式可以隨時取用。 一個變數代表一個分配好的記憶體空間,資料就存放在這個記憶體空間中,使用變數名稱來取得資料會比直接使用記憶體位址來得容易;由於資料在儲存時所需要的空間各不相同,不同的資料必須要配給不同大小的記憶體空間來儲存,才不會造成浪費,Java有幾種不同的「資料型態」(Data type)來配置不同的記憶體空間。 在 Java 中基本的資料型態(Primitive type)主要區分為「整數」、「位元組」、「浮點數(小數)」、「字元」與「布林」,而這幾種還可以細分,如下所示: - 整數 - `short`、`int`、`long` 只儲存整數數值,可細分為短整數(short)(佔 2 個位元組)、整數(int)(佔4個位元組)與長整數(long)(佔 8 個位元組),長整數所佔的記憶體比整數來得多,可表示的數值範圍也就較大,同樣的,整數(int)可表示的整數數值範圍也比短整數來得大。 - 位元組 - `byte` Java 提供有位元(byte)資料型態,可專門儲存位元資料,例如影像的位元資料,一個位元資料型態佔一個位元組,而必要的話,byte 資料型態也可以用於儲存整數數值。 - 浮點數(小數)- `float`、`double` 主要用來儲存小數數值,也可以用來儲存範圍更大的整數,可分為浮點數(float)(佔 4 個位元組)與倍精度浮點數(double)(佔 8 個位元組),倍精度浮點數所使用的記憶體空間比浮點數來得多,可表示的數值範圍與精確度也比較大。 - 字元 - `char` 用來儲存字元,Java 的字元採 Unicode 編碼,其中前 128 個字元編碼與 ASCII 編碼相容;每個字元資料型態佔兩個位元組,可儲存的字元範圍由 '\u0000'到'\uFFFF',由於 Java 的字元採用 Unicode 編碼,一個中文字與一個英文字母在 Java 中同樣都是用一個字元來表示。 - 布林 - `bool` 佔記憶體 2 個位元組,可儲存 true 與 false 兩個數值,分別表示邏輯的「真」與「假」。 因為每種資料型態所佔有的記憶體大小不同,因而可以儲存的數值範圍也就不同,例如整數(int)的記憶體空間是 4 個位元組,所以它可以儲存的整數範圍為 -2147483648 至 2147483647,如果儲存值超出這個範圍的話稱之為「溢位」(Overflow),會造成程式不可預期的結果。 取得資料型態的儲存範圍: ``` public class App { public static void main(String[] args) throws Exception { System.out.printf("short \t範圍:%d ~ %d\n", Short.MAX_VALUE, Short.MIN_VALUE); System.out.printf("int \t範圍:%d ~ %d\n", Integer.MAX_VALUE, Integer.MIN_VALUE); System.out.printf("long \t範圍:%d ~ %d\n", Long.MAX_VALUE, Long.MIN_VALUE); System.out.printf("byte \t範圍:%d ~ %d\n", Byte.MAX_VALUE, Byte.MIN_VALUE); System.out.printf("float \t範圍:%e ~ %e\n", Float.MAX_VALUE, Float.MIN_VALUE); System.out.printf("double \t範圍:%e ~ %e\n", Double.MAX_VALUE, Double.MIN_VALUE); } } ``` 其中 Byte、Integer、Long、Float、Double 都是 java.lang 套件下的類別名稱,而 MAX_VALUE 與 MIN_VALUE 則是各類別中所定義的靜態常數成員,分別表示該資料型態可儲存的數值最大與最小範圍,'%e' 表示用科學記號顯示。 執行結果如下: ``` short 範圍:32767 ~ -32768 int 範圍:2147483647 ~ -2147483648 long 範圍:9223372036854775807 ~ -9223372036854775808 byte 範圍:127 ~ -128 float 範圍:3.402823e+38 ~ 1.401298e-45 double 範圍:1.797693e+308 ~ 4.900000e-324 ``` ### 變數 資料是儲存在記憶體中的一塊空間中,為了取得資料,必須知道這塊記憶體空間的位址,但使用記憶體位址編號的話相當的不方便,所以使用一個明確的名稱,變數(Variable)來表示一個記憶體空間,會更為容易使用;將資料指定給變數,相當於將資料儲存至對應的記憶體空間,呼叫變數時,就是將對應的記憶體空間的資料取出來給程式碼使用。 使用變數前,必須先宣告變數名稱與資料型態,例如: ``` int age; // 宣告一個整數變數 double scope; // 宣告一個倍精度浮點數變數 ``` 就如上面所舉的例子,您使用 int、float、double、char 等關鍵字(Keyword)來宣告變數名稱並指定其資料型態,變數在命名時有一些規則: - 不可以使用數字作為開頭 - 不可以使用一些特殊字元,像是 *&^% 之類的字源,但可以使用底線「_」。 - 不可以與Java的關鍵字同名,例如 int、float、class 等。 - 大寫字母和小寫字母為不同的變數。 > **補充** > > 在變數的命名上,初學者為了方便,常使用一些簡單的字母來作為變數名稱,這會造成日後程式碼維護上的困難,命名變數時發生同名的情況也會增加。 > > 建議將變數命名為跟變數資料相關的名稱。 Java在變數命名上習慣將第一個字母小寫,之後的每個單字第一個字母皆為大寫,例如: ``` int score;int countOfPeople; ``` 像這樣的名稱可以讓人一眼就看出這個變數的作用。 變數名稱可以使用底線作為開始,通常使用底線作為開始的變數名稱,表示它是私用的(Private),只在程式的某個範圍使用,外界並不需要知道有這個變數的存在,通常這樣的變數名稱常用於物件導向程式設計中表示類別的私有成員(Private member),不提供給外部存取,例如: ``` double _name; double _total_items; ``` 在Java中宣告一個變數,就會配置一塊記憶體空間給該變數名稱,這塊空間中原先可能就有資料,所以該變數內的值是不可預期的,Java對於安全性的要求極高,不可以宣告變數後沒有做初始化(指定值)就使用它;如果變數沒有經過初始化,編譯器在編譯時會回報這個錯誤,並顯示 ``` variable var might not have been initialized ``` 儘可能在變數宣告後就初始化該變數,透過「指派運算子」(Assignment operator)`=`來指定變數的初始值,例如: ```java int count = 0; double score = 0.0; char sign = 'A'; ``` 在程式碼裡面使用字串,字串必須使用雙引號包圍著,而字元的話則是使用單引號來包圍,代表只有一個字元。;在指定浮點數時,會習慣使用小數的方式來指定,如 0.0,其預設為 double 資料型態,在數字後面加上一個`F`則代表該數字為float型態。 在宣告變數之後,就可以呼叫變數名稱來取得其所儲存的值,例如: ``` public class App { public static void main(String[] args) throws Exception { int count = 99; double score = 3.3; char sign = 'A'; System.out.println("總共:" + count); System.out.println("分數:" + score); System.out.println("標記:" + sign); } } ``` 執行結果: ``` 總共:99分數:3.3標記:A ``` ### 常數 在程式碼中直接寫下一個數值或一個字串,這個稱之為字面常數(Literal constant),存在記憶體的某個位置,但無法改變它的值;而在使用變數的時候,也會使用一種稱為「常數」的變數,這種常數其實也是變數,只是在指定值給該變數後,就無法再改變其內容,稱為「常數變數」。 要使用常數變數,只要在宣告變數名稱前面加上 `final`關鍵字就可以了,只不過這個變數一但指定了值,就不可以再被改變內容,如果程式中有其它程式碼試圖改變這個變數的內容,編譯時會出現錯誤,例如: ``` final int count = 10;count = 20; ``` 編譯時會出現下面錯誤: ``` cannot assign a value to final variable count ``` 使用`final`來限定的變數,通常是為了給一個常數名稱,減少魔術數字,讓工程師容易理解該常數的用途。 > **補充** > > 在程式設計中,如果沒有解釋就出現了一個不知道其含義的數字,那麼那個數字就可以稱為魔術數字(magic number)。在閱讀程式碼時,因為只能看到該數字得值,所以容易讓人不清楚數值的用途及意義。而且如果該數字在程式中出現多次,在需要修改時也會有需要修改多次,容易造成漏掉修改的問題。 ## 運算 ### 算術運算 在Java中可以透過算術運算子 (Arithmetic operator)來做運算,有加(+)、減(-)、乘(*)、除(/)和餘數運算子(%)。 算術運算子的使用基本上與您學過的加減乘除一樣,也是先乘除後加減,必要時加上括號表示運算的先後順序,例如: ``` System.out.println(1 + 2 * 3); ``` 編譯器在讀取程式碼時是由左往右讀取的,當你想要1+2+3然後再除以4的話,你可以會這樣寫: ``` System.out.println(1 + 2 + 3 / 4); ``` 但實際上會是這樣運算的:1 + 2 + ( 3 / 4);為了避免這樣的錯誤,在必要的時候為運算式加上括號比較保險,例如: ``` System.out.println((double)(1 + 2 + 3) / 4); ``` > 注意在上面的程式碼中使用了 double 型態轉換,如果不加上這個轉型的話,程式的輸出會是 1 而不是 1.5,因為在這個 Java 程式中,1、2、3、4 這四個數值都是整數,當程式運算 (1+2+3) 後的結果還會是整數型態,若此時除以整數 4,會自動去除小數點之後的數字變成整數後才進行輸出,因此需要加上 double 轉型,表示要將 (1+2+3) 運算後的值轉換為 double 資料型態,如此再除以 4,小數點之後的數字才不會被去除。 下面程式碼也是一樣的問題: ``` int total = 10;System.out.println(total / 3); ``` 會輸出 3,而不是3.333,小數點之後的部份被自動消去了,這是因為total 是整數,而除數3也是整數,運算出來的程式被自動轉換為整數了,為了解決這個問題,需要做轉型: ``` int total = 10;System.out.println(total / 3.0); ``` 或 ``` int total = 10;System.out.println((double) total / 3); ``` 只要運算式中有一個浮點數,則程式就會轉換使用浮點數來運算,這是第一段陳述句所使用的方式;第二個方式稱之為「型態轉換」,使用 double 告訴程式先將 total 的值轉換為 double,然後再進行除法運算,所以得到的結果會是正確的 3.3333;型態轉換的關鍵字就是宣告變數時所使用的 int、float 等關鍵字。 當您將精確度小的資料型態(例如int)指定給精確度大的資料型態之變數時(例如 double),這樣的指定在精確度並不會失去,所以這樣的指定是可行的,由於 Java 對於程式的安全性要求極高,型態轉換在某些情況一定要明確指定,就是在使用指定運算子 '=' 時,將精確度大的值指定給精確度小的變數時,由於在精確度上會有遺失,編譯器會認定這是一個錯誤,例如: ``` int total = 0; double test = 3.14; total = test; System.out.println(total); ``` 這段程式在編譯時會出現以下的錯誤訊息: ``` possible loss of precisionfound : doublerequired: int total = test ``` 如果確定要轉換數值為較小的精度,必須明確加上型態轉型,編譯器才不會回報錯誤。 ``` total = (int)test; ``` `%` 運算子是餘除運算子,作用為取得除法運算後的餘數,例如: ``` System.out.println(10 % 2); System.out.println(10 % 3); System.out.println(10 % 4); ``` 會輸出: ``` 012 ``` 下面程式碼用來示範全部算術運算子: ``` import java.io.*; public class App { public static void main(String[] args) throws Exception { BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(System.in)); System.out.print("請輸入第一串文字: "); String text1 = bufferedReader.readLine(); int num1 = Integer.parseInt(text1); System.out.print("請輸入第二串文字: "); String text2 = bufferedReader.readLine(); int num2 = Integer.parseInt(text2); System.out.printf("%d + %d = %d\n", num1, num2, num1 + num2); System.out.printf("%d - %d = %d\n", num1, num2, num1 - num2); System.out.printf("%d * %d = %d\n", num1, num2, num1 * num2); System.out.printf("%d / %d = %d\n", num1, num2, num1 / num2); System.out.printf("%d %% %d = %d\n", num1, num2, num1 % num2); } } ``` ### 比較運算 數學上有比較的運算,像是大於、等於、小於等運算,Java 中也提供了這些運算子,這些運算子稱之為「比較運算子」(Comparison operator),它們有大於(>)、不小於(>=)、小於(<)、不大於(<=)、等於(==)以及不等於(!=)。 在 Java 中,比較的條件成立時以 true 表示,比較的條件不成立時以 false 表示,範例 3.14 示範了幾個比較運算的使用。 ``` public class App { public static void main(String[] args) { System.out.println("10 > 5 結果 " + (10 > 5)); System.out.println("10 >= 5 結果 " + (10 >= 5)); System.out.println("10 < 5 結果 " + (10 < 5)); System.out.println("10 <= 5 結果 " + (10 <= 5)); System.out.println("10 == 5 結果 " + (10 == 5)); System.out.println("10 != 5 結果 " + (10 != 5)); } } ``` 程式的執行如下所示: ``` 10 > 5 結果 true 10 >= 5 結果 true 10 < 5 結果 false 10 <= 5 結果 false 10 == 5 結果 false 10 != 5 結果 true ``` 比較運算在使用時有個即使是程式設計老手也可能犯的錯誤,且不容易發現,也就是等於運算子 '==',注意它是兩個連續的等號 '=' 所組成,而不是一個等號,一個等號是指定運算,這點必須特別注意,例如若有兩個變數x與y要比較是否相等,應該是寫成 x == y,而不是寫成 x = y,後者的作用是將y的值指定給 x,而不是比較運算 x 與 y 是否相等。 另一個使用 `==` 運算時要注意的是,對於物件來說,兩個物件參考之間使用 `==` 作比較時,是比較其名稱是否參考至同一物件,而不是比較其內容,在之後的章節介紹 Integer 等包裹(Wrapper)物件或字串時會再詳細介紹。 ### 條件運算 既然談到了條件式的問題,來介紹 Java 中的「條件運算子」(conditional operator),它的使用方式如下: ``` 條件式 ? 成立傳回值 : 失敗傳回值 ``` 條件運算子的傳回值依條件式的結果而定,如果條件式的結果為 true,則傳回冒號 ':' 前的值,若為 false,則傳回冒號後的值,例如: ``` import java.io.*; public class App { public static void main(String[] args) throws Exception { BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); System.out.print("請輸入學生分數: "); int scoreOfStudent = Integer.parseInt(br.readLine()); System.out.println("該生是否及格? " + (scoreOfStudent >= 60 ? '是' : '否')); } } ``` 這個程式會依您所輸入的分數來判斷學生成績是否不小於 60 分,以決定其是否及格,如果是則傳回字元 '是' ,否則傳回字元 '否',執行結果如下: ``` 請輸入學生分數: 88 該生是否及格? 是 ``` 條件運算子(?:)使用得當的話可以少寫幾句程式碼,下面範例判斷使用者輸入是否為奇數: ``` import java.io.*; public class App { public static void main(String[] args) throws Exception { BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); System.out.print("請輸入數字: "); int number = Integer.parseInt(br.readLine()); System.out.println("是否為奇數? " + number%2 != 0 ? '是' : '否')); } } ``` 當您輸入的數為奇數時,就不能被2整除,所以餘數一定不是 0,在條件式中判斷為 true,因而傳回字元 '是',若數值為偶數,則 2 整除,所以餘數為 0,在條件式中判斷為 false,所以傳回字元 '否',一個執行的例子如下: ``` 請輸入數字: 87是否為奇數? 是 ``` **練習** 寫一程式,使用者可以透過標準輸入來提供一個整數,執行後告知使用者該數字為奇數或偶數: ``` import java.io.*; public class App { public static void main(String[] args) throws Exception { BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(System.in)); System.out.print("請輸入一個數字: "); String text = bufferedReader.readLine(); int num = Integer.parseInt(text); if(num % 2 == 0) { System.out.println("您輸入的是偶數"); } else { System.out.println("您輸入的是奇數"); } } } ``` 猜數字,寫一成是讓使用者可以猜0~9之間的整數,如果猜對了就顯示"猜對了", 否則就是猜錯了。 ``` import java.io.*; public class App { public static void main(String[] args) throws Exception { int answer = (int) ((Math.random() * 10)); // 隨機取得0~9之間整數 System.out.println("答案是: " + answer); // 印出答案,為了測試用 BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(System.in)); System.out.print("請輸入一個數字: "); String text = bufferedReader.readLine(); int num = Integer.parseInt(text); if(answer == num) { System.out.println("猜對了"); } else { System.out.println("猜錯了"); } System.out.println("猜" + (answer == num ? "對了":"錯了")); } } ``` ### 邏輯運算 大於、小於的運算會了,但如果想要同時進行兩個以上的條件判斷呢?例如分數大於 80 「且」小於 90 的判斷。在邏輯上有 所謂的「且」(And)、「或」(Or)與「反」(Inverse),在 Java 中也提供這幾個基本邏輯運算所需的「邏輯運算子」(Logical operator),分別為「且」(&&)、「或」(||)及「反相」(!)三個運算子。 來看看範例 3.17 會輸出什麼? ``` public class LogicalOperator { public static void main(String[] args) { int number = 75; System.out.println((number > 70 && number < 80)); System.out.println((number > 80 || number < 75)); System.out.println(!(number > 80 || number < 75)); } } ``` 三段陳述句分別會輸出 true、false 與 true 三種結果,分別表示 number 大於 70「且」小於 80 為真、number 大於 80「或」小於 75 為假、number 大於 80「或」小於 75 的「相反」為真。 **練習** 寫一程式,輸入一個年份,幫忙判斷是不是閏年。 > 閏年定義: 年份可以被4整除,但不能被100整除 ``` import java.io.*; public class App { public static void main(String[] args) throws Exception { BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(System.in)); System.out.print("請輸入一個年份: "); String text = bufferedReader.readLine(); int num1 = Integer.parseInt(text); if(num1 % 4 == 0 && num1 % 100 != 0) { System.out.println(num1 + "年是閏年"); } else { System.out.println(num1 + "年不是閏年"); } } } ``` ### 位元運算 接下來看看「位元運算子」(Bitwise operator),在數位設計上有 AND、OR、NOT、XOR 與補數等運算,在 Java 中提供這些運算的就是位元運算子,它們的對應分別是&(AND)、|(OR)、^(XOR)與~(補數)。 如果您不會基本的位元運算,可以從下面範例中瞭解各個位元運算的結果: ``` public class BitwiseOperator { public static void main(String[] args) { System.out.println("AND運算:"); System.out.println("0 AND 0\t\t" + (0 & 0)); System.out.println("0 AND 1\t\t" + (0 & 1)); System.out.println("1 AND 0\t\t" + (1 & 0)); System.out.println("1 AND 1\t\t" + (1 & 1)); System.out.println("\nOR運算:"); System.out.println("0 OR 0\t\t" + (0 | 0)); System.out.println("0 OR 1\t\t" + (0 | 1)); System.out.println("1 OR 0\t\t" + (1 | 0)); System.out.println("1 OR 1\t\t" + (1 | 1)); System.out.println("\nXOR運算:"); System.out.println("0 XOR 0\t\t" + (0 ^ 0)); System.out.println("0 XOR 1\t\t" + (0 ^ 1)); System.out.println("1 XOR 0\t\t" + (1 ^ 0)); System.out.println("1 XOR 1\t\t" + (1 ^ 1)); } } ``` 執行結果就是各個位元運算的結果: ``` AND運算: 0 AND 0 0 0 AND 1 0 1 AND 0 0 1 AND 1 1 OR運算: 0 OR 0 0 0 OR 1 1 1 OR 0 1 1 OR 1 1 XOR運算: 0 XOR 0 0 0 XOR 1 1 1 XOR 0 1 1 XOR 1 0 ``` Java 中的位元運算是逐位元運算的,例如 10010001 與 01000001 作 AND 運算,是一個一個位元對應運算,答案就是 00000001;而補數運算是將所有的位元 0 變 1,1 變 0,例如 00000001 經補數運算就會變為 11111110,例如下面這個程式所示: ``` byte number = 0; System.out.println((int)(~number)); ``` 這個程式會在主控台顯示 -1,因為 byte 佔記憶體一個位元組,它儲存的 0 在記憶體中是 00000000,經補數運算就變成 11111111,這個數在電腦中用整數表示則是 -1。 > **注意** > > 邏輯運算子與位元運算子很常被混淆,像是「&&」與「&」,「||」與 「|」,初學時可得多注意。 位元運算對初學者來說的確較不常用,但如果用的恰當的話,可以增進不少程式效率,下面範例可以判斷使用者的輸入是否為奇數: ``` import java.io.*; public class App { public static void main(String[] args) throws Exception { BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); System.out.print("請輸入數字: "); int number = Integer.parseInt(br.readLine()); System.out.println("是否為奇數? " + ((number & 1) != 0 ? '是' : '否')); } } ``` 執行結果: ``` 請輸入數字: 66 是否為奇數? 否 ``` 運作的原理是,奇數的數值若以二進位來表示,其最右邊的位元必為 1,而偶數最右邊的位元必為 0,所以您使用1來與輸入的值作 AND 運算,由於 1 除了最右邊的位元為 1 之外,其它位元都會是 0,與輸入數值 AND 運算的結果,只會留下最右邊位元為 0 或為 1 的結果,其它部份都被 0 AND 運算遮掉了,這就是所謂「位元遮罩」,例如 4 與 1 作 AND 運算的結果會是 0,所以判斷為偶數: ``` 整數4:00000100 整數1:00000001 AND 運算後:00000000 ``` 而 3 與 1 作 AND 運算的結果是 1,所以判斷為奇數: ``` 整數3:00000011 整數1:00000001 AND運算後:00000001 ``` XOR 的運算較不常見,XOR 字元加密例子範例: ``` public class App { public static void main(String[] args) { final int key = 9487945; // 加解密用的key char ch = 'A'; System.out.println("編碼前:" + ch); ch = (char)(ch^key); System.out.println("編碼後:" + ch); ch = (char)(ch^key); System.out.println("解碼:" + ch); int data = 3939889; System.out.println("編碼前:" + data); data = (int)(data^key); System.out.println("編碼後:" + data); data = (int)(data^key); System.out.println("解碼:" + data); } } ``` 0x7 是 Java 中整數的 16 進位寫法,其實就是 10 進位的 7,將位元與 1 作 XOR 的作用其實就是位元反轉,0x7 的最右邊三個位元為 1,所以其實就是反轉 ch 變數的最後兩個位元,如下所示: ``` ASCII 中的 'A' 字元編碼為 65:01000001 整數 7:00000111XOR 運算後:01000110 ``` 01000110 就是整數 70,對應 ASCII 中的字元 'F' 之編碼,所以用字元方式顯示時會顯示 'F' 字元,同樣的,這個簡單的 XOR 字元加密,要解密也只要再進行相同的位元反轉就可以了,看看範例 3.20 的執行結果: ``` 編碼前:A 編碼後:예 解碼:A 編碼前:3939889 編碼後:11327608 解碼:3939889 ``` 在位元運算上,Java 還有左移(<<)與右移(>>)兩個運算子,左移運算子會將所有的位元往左移指定的位數,左邊被擠出去的位元會被丟棄,而右邊會補上0;右移運算則是相反,會將所有的位元往右移指定的位數,右邊被擠出去的位元會被丟棄,至於最左邊補上原來的位元,如果左邊原來是 0 就補 0,1 就補 1。另外還有 >>> 運算子,這個運算子在右移後,一定在最左邊補上 0。 使用左移運算來作簡單的2次方運算示範: ``` public class App { public static void main(String[] args) { int number = 1; System.out.println( "2的0次方: " + number); number = number << 1; System.out.println("2的1次方: " + number); number = number << 1; System.out.println("2的2次方: " + number); number = number << 1; System.out.println("2的3次方:" + number); } } ``` 執行結果: ``` 2的0次方: 1 2的1次方: 2 2的2次方: 4 2的3次方: 8 ``` 實際來左移看看就知道為何可以如此作次方運算了: ``` 00000001 -> 1 00000010 -> 2 00000100 -> 4 00001000 -> 8 ``` > 位元運算對於沒有學過數位邏輯的初學者來說,會比較難一些,基本上除了像是資訊工程、電機工程相關領域的開發人員會比較常使用位元運算之外,大部份的開發人員可能不常使用位元運算,如果您的專長領域比較不需要使用位元運算,則基本上先瞭解有位元運算這個東西就可以了,不必在這個部份太過鑽研。 ### 遞增、遞減運算子 遞增(Increment)、遞減(Decrement)與指定(Assignment)運算子,老實說常成為初學者的一個惡夢,因為有些程式中若寫得精簡,這幾個運算子容易讓初學者搞不清楚程式的真正運算結果是什麼;事實上,使用這幾種運算子的目的除了使讓程式看來比較簡潔之外,還可以稍微增加一些程式執行的效率。 在程式中對變數遞增1或遞減1是很常見的運算,例如: ``` int i = 0; i = i + 1; System.out.println(i); i = i - 1; System.out.println(i); ``` 這段程式會分別顯示出 1 與 0 兩個數,您可以使用遞增、遞減運算子來撰寫程式: ``` public class IncrementDecrement { public static void main(String[] args) { int i = 0; System.out.println(++i); System.out.println(--i); } } ``` 其中寫在變數 i 之前的 ++ 與 -- 就是「遞增運算子」(Increment operator)與「遞減運算子」(Decrement operator),當它們撰寫在變數之前時,其作用就相當於將變數遞增 1 與遞減 1: ``` ++i; // 相當於 i = i + 1; --i; // 相當於 i = i - 1; ``` 您可以將遞增或遞減運算子撰寫在變數之前或變數之後,但其實兩種寫法是有差別的,將遞增(遞減)運算子撰寫在變數前時,表示先將變數的值加(減)1,然後再傳回變數的值,將遞增(遞減)運算子撰寫在變數之後,表示先傳回變數值,然後再對變數加(減)1,例如: ``` public class App { public static void main(String[] args) { int i = 0; int number = 0; number = ++i; // 相當於i = i + 1; number = i; System.out.println(number); number = --i; // 相當於i = i - 1; number = i; System.out.println(number); } } ``` 在這段程式中,number 的值會前後分別顯示為 1 與 0,再看下面範例 : ``` public class App { public static void main(String[] args) { int i = 0; int number = 0; number = i++; // 相當於number = i; i = i + 1; System.out.println(number); number = i--; // 相當於 number = i; i = i - 1; System.out.println(number); } } ``` 在這段程式中,number 的值會前後分別顯示為 0 與 1。 ### 指派運算子 接下來看「指定運算子」(Assignment operator),到目前為止您只看過一個指定運算子,也就是 '=' 這個運算子,事實上指定運算子還有以下的幾個: | 指定運算子 | 範例 | 結果 | | ---------- | ------- | ---------- | | += | a += b | a = a + b | | -= | a -= b | a = a - b | | *= | a *= b | a = a * b | | /= | a /= b | a = a / b | | %= | a %= b | a = a % b | | &= | a &= b | a = a & b | | \|= | a \|= b | a = a \| b | | ^= | a ^= b | a = a ^ b | | <<= | a <<= b | a = a << b | | >>= | a >>= b | a = a >> b | 每個指定運算子的作用如上所示,但老實說若不是常寫程式的老手,當遇到這些指定運算子時,有時可能會楞一下,因為不常用的話,這些語法並不是那麼的直覺。 使用 ++、- - 或指定運算子,由於程式可以直接在變數的記憶體空間中運算,而不用取出變數值、運算再將數值存回變數的記憶體空間,所以可以增加運算的效率,但以現在電腦的運算速度來看,這一點的效率可能有些微不足道,除非您這類的運算相當的頻繁,否則是看不出這點效率所帶來的改善,就現在程式撰寫的規模來看,程式的易懂易讀易維護會比效率來的重要,可以的話儘量將程式寫的詳細一些會比較好,千萬不要為了賣弄語法而濫用這些運算子。 就單一個陳述而言,使用 ++、- - 或指定運算子是還算可以理解,但與其它陳述結合時可就得考慮一下,例如: ``` int i = 5; arr[--i %= 10] = 10; ``` 像這樣的式子,要想知道變數 i 是多少,以及陣列的指定索引位置在哪可就得想一下了(有興趣算一下的話,i 最後會是 4,而陣列的指定索引也是 4)。 ### 運算子優先權 一般來說,運算子的結合為由左到右做運算,但會因為優先權的不同改變其結合的順序: | 運算子Operators | 優先度Precedence | | ------------------------------- | -------------------------------------------------- | | 小括號、索引、點 | () [] . | | 後綴 postfix | expr++ expr-- | | 單元 unary | ++expr --expr +expr -expr ~ ! | | 乘法 multiplicative | * / % | | 加法 additive | + - | | 位移 shift | << >> >>> | | 關係 relational | < > <= >= instanceof | | 相等 equality | == != | | 位元和 bitwise AND | & | | 位元互斥或 bitwise exclusive OR | ^ | | 位元或 bitwise inclusive OR | \| | | 邏輯和 logical AND | && | | 邏輯或 logical OR | \| \| | | 三元 ternary | ? : | | 指定 assignment | = += -= *= /= %= &= ^= \|= <<= >>= >>>= |