學完本章你將能夠:

  • 使用算術運算子計算,並解釋整數除法與取餘數的行為。

  • 分辨 i++ 與 ++i 的差異,正確使用遞增遞減與複合指定運算子。

  • 使用比較與邏輯運算子組合條件,並解釋短路求值。

  • 避開字串串接與加號混用的經典陷阱。

  • 說明浮點數為什麼會有精度誤差,以及正確的比較方式。

  • 綜合運用本章工具,寫出會互動的 BMI 計算機。

上一章我們學會把資料放進變數,但只會「存放」的程式什麼也做不了——資料 必須被計算、比較、組合,才會產生價值。讓資料動起來的工具就是「運算子」 (operator),而運算子把變數與字面值組合起來的式子稱為「運算式」 (expression),每個運算式最終都會算出一個值。

本章的內容像是一盒工具:每種運算子單獨看都很簡單,但它們彼此搭配時藏著 幾個非常經典的陷阱——整數除法的截斷、字串串接的順序、浮點數的精度誤差。 這些陷阱每年坑掉無數初學者,我們會逐一拆解,並在章末用一個完整的 BMI 計算機把所有工具串起來。

算術運算子

Java 提供五個基本算術運算子:

運算子 意義 範例 結果
|+| 加法 |7 + 2| |9|
|-| 減法 |7 - 2| |5|
|*| 乘法 |7 * 2| |14|
|/| 除法 |7 / 2| |3|(不是 3.5!)
|

其中除法是第一個重點:整數除以整數,結果仍是整數,小數部分被 直接丟棄(向零截斷),不是四捨五入。想得到小數結果,至少要讓其中一邊是 浮點數。

/** * 算術運算子總覽:特別注意整數除法與取餘數的行為。 */ public class ArithmeticDemo

public static void main(String[] args)

int a = 7; int b = 2;

System.out.println("a + b = " + (a + b)); System.out.println("a - b = " + (a - b)); System.out.println("a * b = " + (a * b));

// 整數除法:小數部分直接捨去 System.out.println("a / b = " + (a / b));

// 想要小數結果,至少一邊要是浮點數 System.out.println("a / 2.0 = " + (a / 2.0));

// 取餘數:7 除以 2,商 3「餘 1」 System.out.println("a

// 負數的除法與餘數:商向零截斷,餘數跟著被除數的正負號 System.out.println("-7 / 2 = " + (-7 / 2)); System.out.println("-7

a + b = 9 a - b = 5 a * b = 14 a / b = 3 a / 2.0 = 3.5 a -7 / 2 = -3 -7

取餘數 |

  • 判斷奇偶:|n

  • 判斷整除:|n

  • 取出位數:|n

  • 循環計數:不斷加一的索引對長度取餘數,就能繞圈圈(例如 星期幾、輪播圖)。

除以零會怎樣

數學老師說不能除以零,Java 的態度則分成兩種。浮點數除以零不會讓程式 當掉,而是回傳特殊值;整數除以零則會直接拋出例外、中斷程式:

/** * 整數與浮點數除以零的不同下場。 */ public class DivideByZero

public static void main(String[] args)

// 浮點數除以零:得到特殊值,程式繼續走 System.out.println(1.0 / 0); // 正無限大 System.out.println(-1.0 / 0); // 負無限大 System.out.println(0.0 / 0); // NaN:Not a Number

// 整數除以零:拋出例外,程式立刻中斷! System.out.println(1 / 0);

Infinity -Infinity NaN Exception in thread "main" java.lang.ArithmeticException: / by zero at DivideByZero.main(DivideByZero.java:12)

這是你第一次看到「例外」(exception):執行期發生的錯誤事件,未處理時 會終止程式並印出發生位置。例外處理是中級篇的重頭戲,目前先學會看懂 這種訊息即可——它告訴你錯誤種類(ArithmeticException)、原因 (除以零)與行號(第 12 行)。

遞增與遞減

「把變數加一」是程式裡出現頻率最高的動作,因此有專屬的縮寫: |i++|(後置)與 |++i|(前置),同理有 |i–| 與 |–i|。兩者都讓變數加一,差別在「運算式本身的值」:後置先交出 舊值再加一,前置先加一再交出新值。

/** * 後置 i++ 與前置 ++i 的差異。 */ public class IncrementDemo

public static void main(String[] args)

int a = 5; int b = a++; // 後置:先把舊值 5 交給 b,a 才變 6 System.out.println("a = " + a + ", b = " + b);

int c = 5; int d = ++c; // 前置:c 先變 6,再把新值 6 交給 d System.out.println("c = " + c + ", d = " + d);

// 單獨成一行使用時,前置與後置效果完全相同 int count = 0; count++; ++count; System.out.println("count = " + count);

a = 6, b = 5 c = 6, d = 6 count = 2

最佳實務:讓 |++| 與 |–| 單獨成一行,不要塞進 複雜運算式裡。像 |int x = i++ + ++i;| 這種程式雖然合法, 但沒有人能一眼看懂,程式碼審查時一定會被退件。清楚永遠比炫技重要。

指定與複合指定運算子

|=| 是「指定」運算子:把右邊的值放進左邊的變數,不是數學的 「等於」。搭配算術運算子有一系列縮寫:|a += 5| 等同 |a = a + 5|,其餘 |-=|、|*=|、|/=|、 |

/** * 複合指定運算子:對變數「就地更新」的縮寫。 */ public class CompoundAssignDemo

public static void main(String[] args)

int score = 100;

score += 20; // score = score + 20 System.out.println("加 20 後:" + score);

score -= 50; // score = score - 50 System.out.println("減 50 後:" + score);

score *= 2; // score = score * 2 System.out.println("乘 2 後:" + score);

score /= 3; // 整數除法:140 / 3 = 46 System.out.println("除 3 後:" + score);

score System.out.println("對 7 取餘:" + score);

加 20 後:120 減 50 後:70 乘 2 後:140 除 3 後:46 對 7 取餘:4

複合指定運算子還偷偷附贈了自動轉型:|byte b = 10; b += 5;| 可以編譯,但拆開寫成 |b = b + 5;| 反而錯誤——因為 |b + 5| 的運算結果被提升為 |int|,塞回 byte 需要強制 轉型。複合版本相當於幫你補了 |b = (byte) (b + 5);|。 知道這個細節,未來看到相關的編譯錯誤就不會一頭霧水。

比較運算子

比較運算子把兩個值做比較,產出 |boolean| 結果,是流程控制的 基石:|==|(等於)、|!=|(不等於)、|>|、|<|、 |>=|、|<=|。

/** * 比較運算子:結果一律是 boolean。 */ public class ComparisonDemo

public static void main(String[] args)

int x = 10; int y = 20;

System.out.println("x == y:" + (x == y)); System.out.println("x != y:" + (x != y)); System.out.println("x < y:" + (x < y)); System.out.println("x >= 10:" + (x >= 10));

x == y:false x != y:true x < y:true x >= 10:true

兩個必踩地雷提前排除:第一,|=| 是指定、|==| 才是比較, 寫錯位置輕則編譯錯誤,重則邏輯默默出錯。第二,|==| 只適合 比較基本型別;比較字串內容必須用 |equals()| 方法, 原因與示範見第 7 章——先把這條規則背起來:字串比較不用 兩個等號。

邏輯運算子與短路求值

邏輯運算子把多個 boolean 條件組合成複合條件:|&&|(且)、 !||!(或)、|!|(非)。真值表如下:

@llll@ |a| & |b| & |a && b| & !a || b!
|true| & |true| & |true| & |true|
|true| & |false| & |false| & |true|
|false| & |true| & |false| & |true|
|false| & |false| & |false| & |false|

重點特性是「短路求值」(short-circuit evaluation):|&&| 的左邊 若是 |false|,整體必為 false,右邊根本不會執行; !||! 的左邊若是 |true|,右邊同樣被跳過。下面的程式用 |++x| 當「探測器」證明這件事——如果右邊有執行,x 就會留下痕跡:

/** * 短路求值的證明:用 ++x 偵測右邊運算式到底有沒有被執行。 */ public class ShortCircuitDemo

public static void main(String[] args)

int x = 0;

// && 左邊是 false:右邊被跳過,x 不會加一 boolean r1 = (x > 0) && (++x > 0); System.out.println("r1 = " + r1 + ", x = " + x);

// || 左邊是 true:右邊同樣被跳過 boolean r2 = (x == 0) || (++x > 0); System.out.println("r2 = " + r2 + ", x = " + x);

// 左邊無法決定結果時,右邊才會執行:這次 x 真的加一了 boolean r3 = (x == 0) && (++x > 0); System.out.println("r3 = " + r3 + ", x = " + x);

r1 = false, x = 0 r2 = true, x = 0 r3 = true, x = 1

短路不只是效能優化,更是實用的防禦技巧。經典寫法: |s != null && s.length() > 0|——若 s 是 null,左邊直接判定 false,右邊的 |s.length()| 不會執行,也就不會因為對 null 呼叫 方法而爆炸。條件的順序因此有了意義:把「保鑣」放左邊。

位元運算子簡介

位元運算子直接對整數的二進位位元動手,常見於底層開發、旗標處理與 效能敏感的場合。初學階段認識即可:|&|(位元且)、!|! (位元或)、|^|(互斥或)、| |(取反)、|<<| (左移)、|>>|(右移)。

/** * 位元運算子速覽:以 12(1100)與 10(1010)為例。 */ public class BitwiseDemo

public static void main(String[] args)

int a = 12; // 二進位 1100 int b = 10; // 二進位 1010

System.out.println("a & b = " + (a & b)); // 1000 System.out.println("a | b = " + (a | b)); // 1110 System.out.println("a ^ b = " + (a ^ b)); // 0110 System.out.println(" a = " + ( a)); // 位元全反

// 左移一位等於乘 2,右移一位等於除 2(正整數時) System.out.println("3 << 2 = " + (3 << 2)); System.out.println("16 >> 2 = " + (16 >> 2));

a & b = 8 a | b = 14 a ^ b = 6  a = -13 3 << 2 = 12 16 >> 2 = 4

注意區分:|&&| 與 !||! 是「邏輯」運算子,操作 boolean 且 會短路;單一個 |&| 與 !|! 用在整數時是「位元」運算子。 日常業務邏輯幾乎只用得到邏輯運算子。

三元運算子

三元運算子 |條件 ? 值A : 值B| 是迷你版的二選一:條件成立取 值A,否則取值B。它是「運算式」,會算出一個值,適合簡短的判斷:

/** * 三元運算子:一行完成「二選一」。 */ public class TernaryDemo

public static void main(String[] args)

int score = 75;

// 條件成立取問號後的值,否則取冒號後的值 String result = (score >= 60) ? "及格" : "不及格"; System.out.println(result);

int a = 10; int b = 20; int max = (a > b) ? a : b; // 取較大值 System.out.println("較大值:" + max);

String parity = (a System.out.println(a + " 是" + parity);

及格 較大值:20 10 是偶數

三元運算子可以巢狀,但兩層以上可讀性就會急速惡化,此時應改用下一章的 |if-else| 敘述。原則:三元運算子只留給「一眼能看懂」的場合。

字串串接的加號陷阱

|+| 有雙重身分:兩邊都是數字時做加法;任一邊是字串時, 做字串串接,並把另一邊轉成文字。配上「由左到右」的計算順序,就產生了 每年騙倒無數初學者的經典陷阱:

/** * 加號的雙重身分:加法與字串串接,順序決定一切。 */ public class ConcatTrap

public static void main(String[] args)

// 由左到右:"1" + 2 先變成字串 "12",再接上 3 System.out.println("1" + 2 + 3);

// 由左到右:1 + 2 是數字加法得 3,再與 "3" 串接 System.out.println(1 + 2 + "3");

// 陷阱重現:本想印 95 分,卻印出 905! System.out.println("總分:" + 90 + 5);

// 解法:用括號先完成數學加法 System.out.println("總分:" + (90 + 5));

123 33 總分:905 總分:95

規則整理:|+| 一旦碰到字串,之後整條式子都會被「串接」感染。 |"總分:" + 90 + 5| 的計算過程是:先算 |"總分:" + 90| 得到字串「總分:90」,再接上 5 變成「總分:905」。凡是要在輸出裡 做數學運算,一律加括號,例如 |"總分:" + (90 + 5)|。

運算子優先順序

一條式子有多個運算子時,誰先誰後?Java 的優先順序與數學直覺大致相同 (先乘除後加減),完整順位如下表(同列由左到右結合,指定運算子除外):

@clp11em@ 優先度 & 運算子 & 說明
(最高) & |()| & 括號
2 & |++| |–| |!| | | & 一元運算子
3 & |*| |/| | 4 & |+| |-| & 加、減
5 & |<<| |>>| & 位元移位
6 & |<| |<=| |>| |>=| & 大小比較
7 & |==| |!=| & 相等比較
8 & |&| |^| !|! & 位元邏輯
9 & |&&| & 邏輯且
10 & !||! & 邏輯或
11 & |? :| & 三元運算子
12(最低) & |=| |+=| |-=| 等 & 指定

你不需要背這張表。業界的共識是:不確定就加括號。括號不但 免除記憶負擔,更把你的意圖白紙黑字寫給讀者看。 !(a && b) || (c && d)! 遠比省掉括號的版本清楚,效能上也毫無 差別。

浮點數的精度問題

準備好見證一件顛覆直覺的事了嗎?在 Java 裡,|0.1 + 0.2| 不等於 |0.3|。這不是 Java 的臭蟲,而是所有使用 IEEE 754 標準的語言 (包括 Python、JavaScript)的共同宿命:十進位的 0.1 換算成二進位是 無限循環小數,就像十進位無法精確寫出三分之一,儲存時必然有微小誤差。

/** * 浮點數精度誤差:0.1 + 0.2 為什麼不等於 0.3。 */ public class PrecisionDemo

public static void main(String[] args)

double sum = 0.1 + 0.2; System.out.println("0.1 + 0.2 = " + sum); System.out.println("等於 0.3 嗎?" + (sum == 0.3));

// 正確的比較方式:兩數的差距小於容忍值,就視為相等 double epsilon = 1e-9; // 容忍十億分之一的誤差 boolean closeEnough = Math.abs(sum - 0.3) < epsilon; System.out.println("夠接近 0.3 嗎?" + closeEnough);

0.1 + 0.2 = 0.30000000000000004 等於 0.3 嗎?false 夠接近 0.3 嗎?true

因此鐵律有二:永遠不要用 == 直接比較浮點數; 金額運算不要用 double。需要十進位精確計算時,Java 提供 |BigDecimal| 類別:

import java.math.BigDecimal;

/** * BigDecimal:十進位的精確計算,金融系統的標準配備。 */ public class BigDecimalDemo

public static void main(String[] args)

// 務必用「字串」建構!寫 new BigDecimal(0.1) 會把 // double 已經帶有的誤差原封不動搬進來 BigDecimal a = new BigDecimal("0.1"); BigDecimal b = new BigDecimal("0.2");

// BigDecimal 的加法用 add 方法,不是 + 號 System.out.println("精確結果:" + a.add(b));

精確結果:0.3

何時該用什麼:科學計算、遊戲座標、統計分析等「容許微小誤差」的場合, double 又快又省,放心用;金額、費率、發票這類「一分錢都不能錯」的 場合,用 BigDecimal,或改用整數以「分」為單位計算。判斷基準只有 一個問題:這個數字算錯一點點,會不會有人賠錢?

綜合範例:BMI 計算機

把本章工具全部組合起來:用 Scanner 讀取身高體重(第 2 章)、浮點數 除法計算 BMI、|Math.round| 處理四捨五入,最後用三元運算子給出 評語。BMI 的公式是體重(公斤)除以身高(公尺)的平方。

import java.util.Scanner;

/** * BMI 計算機:讀取身高體重,計算 BMI 並給出評語。 */ public class BmiCalculator

public static void main(String[] args)

Scanner scanner = new Scanner(System.in);

System.out.print("請輸入身高(公尺):"); double height = scanner.nextDouble();

System.out.print("請輸入體重(公斤):"); double weight = scanner.nextDouble();

// 全程使用 double 運算,避免整數除法的截斷 double bmi = weight / (height * height);

// 四捨五入到小數點後一位:先放大十倍再縮回來 double rounded = Math.round(bmi * 10) / 10.0; System.out.println("你的 BMI 是:" + rounded);

// 三元運算子串出三段式評語(兩層還算可讀的極限) String comment = (bmi < 18.5) ? "體重偏輕" : (bmi < 24) ? "體重正常" : "體重偏重"; System.out.println("評估結果:" + comment);

scanner.close();

請輸入身高(公尺):1.7 請輸入體重(公斤):65 你的 BMI 是:22.5 評估結果:體重正常

幾個值得回味的細節:|Math.round(bmi * 10) / 10.0| 的分母寫 |10.0| 而不是 |10|,因為 |Math.round| 回傳整數型別, 除以整數 10 又會發生截斷;評語的三元運算子刻意排版成兩行,讓「條件、 結果」對齊——程式的排版也是溝通的一部分。

本章小結

  • 整數除以整數結果仍是整數(向零截斷);想要小數,至少一邊 要是浮點數。

  • |

  • 整數除以零拋出 ArithmeticException;浮點數除以零得到 Infinity 或 NaN。

  • |i++| 先用舊值再加一,|++i| 先加一再用新值; 單獨成行時兩者相同,也建議只單獨成行使用。

  • 複合指定 |+=| 等運算子是就地更新的縮寫,且自帶隱含 轉型。

  • |&&| 與 !||! 有短路求值:左邊能決定結果時, 右邊不執行;可用來把 null 檢查等「保鑣」放在左邊。

  • |+| 碰到字串就變串接,由左到右感染整條式子;輸出中 要做數學運算請加括號。

  • 運算子優先順序記不住沒關係:不確定就加括號。

  • 浮點數有精度誤差:不要用 |==| 比較,改用誤差容忍值; 金額計算用 BigDecimal(以字串建構)。

本章練習

  1. 觀念題:|9 / 2|、|9 分別是什麼?型別又是什麼?(提示:整數除法截斷;有一邊是 double 結果就是 double。)

  2. 觀念題:執行 |int i = 5; int j = i++ + ++i;| 之後, i 與 j 分別是多少?請逐步展開。(提示:i++ 交出 5 後 i 變 6; ++i 先變 7 再交出 7。)

  3. 實作題:寫一個程式判斷某年是否為閏年。規則:能被 4 整除 且不能被 100 整除,或能被 400 整除。用一條含 |&&|、 !||! 的運算式完成,測試 2024(閏年)、1900(平年)、 2000(閏年)。(提示:注意優先順序,善用括號。)

  4. 實作題:讀入一個總秒數(例如 3725),輸出「幾小時幾分幾秒」 (1 小時 2 分 5 秒)。(提示:除法取商、| 兩者搭配。)

  5. 觀念題:為什麼 |0.1 + 0.2 == 0.3| 是 false?如果是 計算購物車金額,你會改用什麼做法?(提示:二進位無法精確 表示 0.1;想想 BigDecimal 或整數計價。)

  6. 挑戰題:擴充 BMI 計算機——加入「過輕、正常、過重、肥胖」 四段評語,並把 BMI 顯示到小數點後兩位。(提示:四捨五入 改成乘除 100;評語可用巢狀三元或改用下一章的 if。)