架構圖

前言
表達式是程式進行算術運算中的表示方式,我們可以簡單地把表達式拆解為表達式 = 運算子 + 操作變數
,也就是說任何運算子都不能脫離變數單獨存在
在java中依據使用功能可以區分成多種不同的運算子,該小節主要聚焦介紹幾種常見的運算子,並探討表達式中若存在多個不同運算子時該如何判斷優先順序
重點運算子
- 算術運算子(Arithmetic Operators)
- 賦值運算子(Assignment Operators)
- 關係運算子(Relational Operators)
- *三目運算子(Ternary Operators)
- 邏輯運算子(Logical Operators)
算數運算子
實現最基本的算術運算。與算術相關的運算子有+, -, *, \/, \%(加、減、乘、除、取餘),整數類型與浮點類型多使用算術運算子進行數學運算,字串與字元則可以使用+進行拼接
案例
int num1 = 10;
int num2 = 20
int num3 = 0;
num3 = 12 + num2;
num3 = num1 * 2;
num3 = num1 / 2;
num3 = num2 % 2;
使用括號
在進行運算時請銘記先乘除後加減法則,如果想要更改運算順序可以透過括號來實現優先運算(優先級部分將會提及)
int num1 = 5;
int num2 = 2;
int num3 = 0;
num3 = num1 + num2 * 2; // 9
num3 = (num1 + num2) * 2; // 14
除法與取餘結果
注意除法運算後的變數類型,例如以下例子,當運算結果為整數類型時,小數點會被無條件捨去,所以在進行相關運算時請特別注意。可以透過轉型使輸出結果轉為浮點類型
int num = 17;
int op1 = 3;
float op2 = 3.0f;
System.out.println("test 1 = " +num/op1); // 5
System.out.println("test 2 = " +num/op2); // 5.6666665
取餘數運算相信大家一定不陌生,當除數具有小數點時,取餘結果也會擁有小數點,當然浮點數運算多少會存在一些誤差值,例如下面例子
float num1 = 17.0f;
float num2 = 17.9f;
float op1 = 3.0f;
System.out.println("test 1 = " +num1%op1); // 2.0
System.out.println("test 2 = " +num2%op1); // 2.8999996
可以在運算子左右兩側留下空白增加易讀性
單目運算子
與上述我們介紹的表達式不同,加減乘除運算均需要兩個或以上的變數參與,而單目運算子僅需要一個變數與一個運算子就可以進行運算
常見的單目算術運算子包括變數正負號表示、遞增與遞減運算:
/* 正負號表示 */
int num1 = -10;
int num2 = +15;
/* 自增與自減運算 */
System.out.println(num1++); // -10
System.out.println(--num2); // 14
常見的遞增運算可以分為前置與後置兩種,在操作上稍微有些不同
前置遞增、遞減
先進行遞增與遞減運算,然後回傳變數值:
int num = 10;
int result = 0;
result = ++num;
System.out.println("result = " + result);
System.out.println("num = " + num);
運算結果:
result = 11
num = 11
後置遞增、遞減
先回傳變數值,在進行遞增或遞減運算:
int num = 10;
int result = 0;
result = num--;
System.out.println("result = " + result);
System.out.println("num = " + num);
運算結果:
result = 10
num = 9
我們再來舉幾個小例子來練練手:
int a = 4;
a = (++a) + 4; // a = 5 + 4
System.out.println("a = "+a);
a = (a++) + 4; // a = 9 + 4
System.out.println("a = "+a);
a += (++a); // a = 13 + 14
System.out.println("a = "+a);
a -= (a--); // a = 27 - 27
System.out.println("a = "+a);
運算結果:
a = 9
a = 13
a = 27
a = 0
實際編寫程式時應當盡量避免以下這種風格寫法,也就是把大量賦值運算、算術運算、遞增遞減運算混合在一起。除了降低可讀性外還可以造成賦值錯誤
int num = 10;
int i = 5;
/* 盡量避免使用這種風格的寫法 */
num += i++;
num += ++i;
num = (num++) * (num++);
num = (++num) + (num++);
指定運算子
最常見的指定運算子莫過於=,它將等號右側的數值賦值給等號左側的變數
int num1;
char num2;
String num3;
num1 = 15;
num2 = 'a';
num3 = "string...";
特別注意賦值方向又由右向左,等號只有一個,且左值必須為一個變數,下面的例子是一些錯誤的寫法:
100.0d = d;
num1 == 0;
num1 + num2 = 5;
複合指定運算子
很多時候賦值運算子會結合算術運算子使用,為了節省語句長度,我們可以將表達式簡寫成特殊的表達形式,例如:
- +=
- -=
- *=
- /=
- %=
這種表達式稱作賦和指定運算子( Compound Statement),除了算術運算子之外,還可以結合位元運算子(之後的章節會提到)
int num1 = 10;
float num2 = 122.6.f;
double num3 = 1.0;
num3 += num1; // 1.0 + 10
num3 = 1.0;
num3 -= num1; // 1.0 - 10
num3 = 1.0;
num3 *= num2; // 1.0 * 122.6
num3 = 1.0;
num3 /= num2; // 1.0 / 122.6
num3 = 1.0;
num3 %= num1; // 1.0 % 10
num3 = 1.0;
雙目運算子的賦值方向
在雙目運算子的操作運算中,操作順序始終是由左至右的,也就是說等號右側的一連串運算一定是由左側發起
int num1 = 3;
int num2 = (num1=4) * num1; // num1已經被改為4,所以執行運算為4 * 4
System.out.println(num2);
輸出結果:
num2 = 16
同理複合指定運算子也適用這個規則
int num = 9;
num += (num=10); // 9 + 10
System.out.println("num = " + num);
num = (num=3) + num; // 3 + 3
System.out.println("num = " + num);
num -= (num=1); // 6 - 1
System.out.println("num = " + num);
輸出結果:
num = 19
num = 6
num = 5
因此每次進行表達式運算時,都從等號右側開始運算,運算規則始終是左至右,了解這個規則後,我們把將多個複合指定運算子結合起來看看輸出效果如何
int num = 10;
num += num -= num *= num /= num;
/* 相當於 */
num = num + (num - (num * (num / num)));
輸出結果:
num = 10
關係運算子
關係運算子多用在條件判斷與循環語句中,例如if, while等,為了要使判斷語句執行,必須判斷執行或不執行,因此可以得知關係運算子的輸出結果不是true就是false
int num = 10;
if(num < 100)
System.out.println(num < 100);
else
System.out.println(num < 100);
輸出結果:
true
java提供多組關係運算子給條件語句進行判斷
- >
- \<
- >=
- \<=
- ==
- !=
另外關係運算子的判斷不限於整數型態,例如下面例子就是整數與浮點數關係運算子範例,只要兩個變數值相同就會返回true
int num1 = 10;
float num2 = 10.0f;
if(num1 == num2){
System.out.println(num1 == num2);
}
else{
System.out.println(num1 == num2);
}
輸出結果:
true
除此之外字元也是一個常見的判斷變數,字元形式主要利用ASCII碼進判斷
char ch = 'a'; // a ASCII碼為97
if(ch >= 100)
System.out.println(ch > 100);
else
System.out.println(ch > 100);
輸出結果:
false
另外布林、字串字面值也可以進行判斷,不過僅限於==與!=
System.out.println(10 <= 100);
System.out.println(true == false);
System.out.println("123" != "124");
輸出結果:
true
false
true
三目運算子
所謂三目運算子,是指條件式與計算返回值共有三個不同的區塊。java提供的三目運算子解析如下:條件判斷 ? 成立返回值 : 失敗返回值
舉幾個例子比較好懂:
int num1=6, num2=9;
boolean b = num1 > num2 ? (13 > 6) : (true == false);
System.out.println(b);
輸出結果:
false
首先判斷條件語句num1 > num2
是否成立,若成立則返回第一個區塊的值,也就是(13 > 6)
,若不成立則返回第二個區塊的值(true == false)
假如我們把三目運算子寫成if-else判斷式的話,就可以看出三目運算子的優勢在於簡潔性了。不過若是牽涉到多條判斷語句(if-else if-else)還是要乖乖地使用判斷式
int num1=6, num2=9;
boolean b=false;
if(num1 > num2)
b = (13 > 6);
else
b = (true == false);
System.out.println(b);
接下來我們把三目運算子的返回值雙雙填入另一個三目運算子,讓判斷式多加一層
int num1 = 18, num2 = 44, num3 = 90;
int max;
max = ((num1 > num2) ? (num1 > num3) ? num1 : num3 : (num2 > num3) ? num2 : num3);
System.out.println("最大值是: " + max);
輸出結果:
最大值是: 90
首先判斷num1 > num2
是否成立,若成立則只需要再判斷num1 > num3
就可以;相反的若是不成立,則需要考慮num2 > num3
是否成立
邏輯運算子
前一小節中我們有提到關係運算子與判斷語句的關係,如果關係運算成立就執行該條語句,但假如需要進行判斷的關係運算語句超過一條,就需要使用邏輯運算子
java主要提供三種邏輯運算子,分別是AND, OR, NOT運算:
- &
- |
- !
int num1 = 10;
int num2 = 101;
if(num1 > 5 & num2 > 100)
System.out.println(num1 >5 & num2 > 100); // true AND ture = true
if(num1 > 10 | num2 > 100) // false OR true = true
System.out.println(num1 > 10 | num2 > 100);
if(!(num1 > 10) && !(num2 < 100)) // NOT(false) AND NOT(false) = true
System.out.println(!(num1 > 10) && !(num2 < 100));
輸出結果:
true
true
true
使用短路運算子提高效率
短路運算子的功能與普通的邏輯運算子相同,它的表示方式為&&
, ||
,都是AND, OR操作,差別在於它不用執行所有的判斷語句。
舉例來說,判斷式A在執行判斷時,遇到第一個關係判斷式為false後就不會再執行(7 > 3)
的判斷了,因為不管無論false如何進行AND運算,結果始終為false
判斷式B也是相同的道理,當(4 < 5)
為true後,不管接下來為false或為true返回值始終為true,所以不需要做額外的判斷
int num = 0;
if((4 > 5) && (7 > 3)){ // A
num++;
}
if((4 < 5) || (5 < 6)){ // B
num++;
}
為了驗證這個運算結果,我們使用普通的邏輯運算子與短路運算子進行比對,發現使用短路運算子確實省略掉判斷式
int i = 5;
if((2*i > 5) | (++i > 2))
System.out.println("i = " + i);
i = 5;
if((2*i > 5) || (++i > 2))
System.out.println("i = " + i);
輸出結果:
i = 6
i = 5
運算子優先級
假如有多條運算子存在一條表達式中,編譯器必須要有一個規則可以區分出誰先做誰後做,例如我們舉一個簡單的運算表達式為例子,很明顯的奉行先成除後加減的規定
double x=1,y=2,z=3;
double d = 2 * x * y + z - 1 / x + z % 2; // 4 + 3 - 1 + 1
System.out.println("d = " + d);
輸出結果:
d = 7.0
但除了普通的算術運算子之外,java還規定了我們上述提及的運算子優先順序,例如常見的指定運算子與邏輯運算子等,下表介紹常見運算子的執行優先順序
優先級 | 結合順序 | 類型 |
---|---|---|
++, — | 右到左 | 算術運算子 |
+(正號), -(負號), ! | 右到左 | 算術運算子 |
/, *, % | 左到右 | 算術運算子 |
+, – | 左到右 | 算術運算子 |
>, <, >=, <= | 左到右 | 關係運算子 |
==, != | 左到右 | 關係運算子 |
& | 左到右 | 邏輯運算子 |
| | 左到右 | 邏輯運算子 |
&& | 左到右 | 邏輯運算子 |
|| | 左到右 | 邏輯運算子 |
?: | 右到左 | 三目運算子(條件運算子) |
=, +=, -=, *=, /=, %= | 右到左 | 複合指定運算子 |
結合順序
所謂結合順序就是運算子會優先向左或右與操作變數結合的特性。例如負號具有由左到右的結合特性a = 4 + - 5
,會先向右側尋找可結合的操作數(常數5)。同理a++
也是先向右側尋找操作數,沒有的話向左側尋找(因為單目運算子的特性)養成使用小括號習慣
雖然我們知道x + y * 2 - 1
的執行順序,但若加上小括號x + (y * 2) - 1
可以讓其他開發者更快讀懂程式碼
運算運算與優先級問題
優先級讓人頭痛的地方就是該如何在一連串運算式抓出誰該先算,誰又該慢點。尤其是遞增遞減運算子的前後置最容易搞混
其實根據官方文件遞增遞減運算子的優先級,後置(post-fix)是高於前置(pre-fix)。下面我們舉個例子:
int num = 5;
System.out.println(num++ * ++num * num++);
輸出結果
245
其實很多人會將優先級高的運算子解釋成先行運算,也就是說他們認為運算順序應該是:num++ -> num++ -> ++num
其實先行運算這個觀念沒有錯,不過前提是該操作變數的前後範圍內,而不會直接就找最高優先級的操作數進行運算。這間接帶出一個觀念,在選擇優先級之前首先要確認整個運算式由左至右的,可以從官方文件中查到這個規則。
說白了就是你要先從左到右進行運算操作,遇到前後均有運算子時才可以進行優先級判斷,而不是直接跳去執行優先級最高的運算式,你可以想像優先級主要是為了處理下面這種場景
int num1 = 5;
int num2 = 6;
int num3 = 1;
int result = 0;
result = num1 * ++num2 * num3++;
result = ((num1 * (++num2)) * (num3++)); // 解釋成這樣
跳回剛剛的例子,若原本的假設成立,編譯器真的會一開始就直接執行優先級高的運算子,而忽略由左到右的這條規則的話,下面這個例子的輸出應該會相等
int num = 5;
System.out.println("test1 = " + (num++ * ++num));
num = 5;
System.out.println("test2 = " + (++num * num++));
輸出結果:
test1 = 35
test2 = 36
看到了吧,輸出結果證明原本的假設不成立,但假如我們考慮到由左到右運算的這條規則時,一切就說得通了
test1的運算順序:
num++
得到返回值5++num
得到返回值7- 兩個操作變數相乘得到35
test2的運算順序
++num
得到返回值6num++
得到返回值6- 兩個操作變數相乘得到36
最後回到原先的例子: num++ * ++num * num++
num++
得到返回值5++num
得到返回值7- 兩個操作變數相乘得到35
num++
得到返回值7- 兩個操作變數相乘得到245