Java 邏輯與位元運算子完全教學:搞懂 &&、|、^、~、位移 >>> 的所有坑點與實例【Thinking in Java 筆記 2-3】
想寫更高效的 Java 程式碼,卻常在 &&
跟 ||
之間猶豫、對 >>>
一臉問號?
這篇文章一次帶你了解 Java 中的邏輯運算子、直接常數、位元運算子與位移運算子,讓你從「這行是什麼意思」的困惑中畢業
不論你是正在讀《Thinking in Java》的學生,還是為了 Leetcode 題目查 >>
和 >>>
差別的工程師,都能在這篇找到答案
Java 邏輯運算子:&&、||、! 的正確用法與短路邏輯
Java 中常用的邏輯運算子有:
&&
(AND)||
(OR)!
(NOT)
這些運算子只能作用在 boolean
表達式上。與 C/C++ 不同,Java 不允許非 boolean 值直接用於邏輯條件中
// C++ 這樣是合法的
while (node) {}
// Java 必須寫成
while (node != null) {}
短路運算(Short-circuiting)
短路邏輯是 Java 中邏輯運算的基本策略,意思是當邏輯運算子左側的條件已足以判定整體結果時,右側的運算就不再執行
&&
(AND):如果左邊是false
,整體結果一定是false
,所以右邊不會執行這||
(OR):如果左邊是true
,整體結果已確定為true
,右邊也不會執行
有助於提高效能,也可避免不必要的副作用或錯誤(例如 &&
右邊包含可能觸發例外的程式碼)
這種「只做必要運算」的特性稱為短路求值(short-circuit evaluation),不只是語法設計,更是程式設計時常用來保護邏輯判斷與效能優化的重要技巧
public class ShortCircuit {
static boolean test1(int val) {
print("test1(" + val + ")");
print("result: " + (val < 1));
return val < 1;
}
static boolean test2(int val) {
print("test2(" + val + ")");
print("result: " + (val < 2));
return val < 2;
}
static boolean test3(int val) {
print("test3(" + val + ")");
print("result: " + (val < 3));
return val < 3;
}
public static void main(String[] args) {
boolean b = test1(0) && test2(2) && test3(2);
print("expression is " + b);
}
}
/* Output:
test1(0)
result: true
test2(2)
result: false
expression is false
*/
在上述程式中,test2(2)
返回 false
,使得整個表達式的結果為 false
,因此 test3(2)
不會被執行
Java 中的直接常數(Literals):十六進位、八進位與浮點數尾碼
直接常數是指在程式碼中直接寫出來的數值,例如 42
、0xFF
、3.14
這些數值字面量。Java 編譯器通常可以根據你寫的位置推斷這些數值是什麼型別,但如果你寫得太曖昧,它會拒絕幫你猜
為了讓程式更清楚明確,也為了避免型別不符錯誤,我們會在某些常數後面加上尾碼(suffix),像是:
1.0f
:加上f
表示這是 float,否則預設是 double123L
:加上L
表示這是 long,否則預設是 int
這樣做可以讓數值常數與變數型別正確對應,避免不必要的型別轉換錯誤,也提升程式可讀性與安全性
Java 支援多種寫法的直接常數:
int i1 = 0x2f; // 十六進位
int i2 = 0177; // 八進位
char c = 0xffff; // 最大 char 值
float f = 1.0f; // float 必須加 f
long l = 123L; // long 建議加 L
public class Literals {
public static void main(String[] args) {
int i1 = 0x2f; // Hexadecimal (lowercase)
print("i1: " + Integer.toBinaryString(i1));
int i2 = 0X2F; // Hexadecimal (uppercase)
print("i2: " + Integer.toBinaryString(i2));
int i3 = 0177; // Octal (leading zero)
print("i3: " + Integer.toBinaryString(i3));
char c = 0xffff; // max char hex value
print("c: " + Integer.toBinaryString(c));
byte b = 0x7f; // max byte hex value
print("b: " + Integer.toBinaryString(b));
short s = 0x7fff; // max short hex value
print("s: " + Integer.toBinaryString(s));
long n1 = 200L; // long suffix
long n2 = 200l; // long suffix (but can be confusing)
long n3 = 200;
float f1 = 1;
float f2 = 1F; // float suffix
float f3 = 1f; // float suffix
double d1 = 1d; // double suffix
double d2 = 1D; // double suffix
// (Hex and Octal also work with long)
}
} /* Output:
i1: 101111
i2: 101111
i3: 1111111
c: 1111111111111111
b: 1111111
s: 111111111111111
*/
超出型別範圍的值會導致編譯錯誤
若編譯器能夠自動識別型別,可以省略型別後綴
指數表示法(Exponential Notation)
指數表示法(Exponential Notation)是一種簡潔表示非常大或非常小數字的方式,特別常見於浮點數的表達。例如:
float expFloat = 1.39e-43f;
double expDouble = 47e47;
這裡的 e
或 E
表示「乘以 10 的某次方」,所以 1.39e-43
就等於 $$1.39 \times 10^{-43}$$
Java 中使用指數表示法的注意事項:
- 大小寫皆可:
e
與E
意義相同,單純視覺差異 - 型別尾碼很重要:若你寫
1.39e-43
而沒加f
,Java 會把它當作double
;若你指定給float
變數,會發生編譯錯誤 - 精度限制:
float
精度有限,使用極小數字(像1.39e-43f
)時可能出現誤差或非預期結果
public class Exponents {
public static void main(String[] args) {
// Uppercase and lowercase 'e' are the same:
float expFloat = 1.39e-43f;
expFloat = 1.39E-43f;
System.out.println(expFloat);
double expDouble = 47e47d; // 'd' is optional
double expDouble2 = 47e47; // Automatically double
System.out.println(expDouble);
}
} /* Output:
1.39E-43
4.7E48
*/
如果你是初學者,簡單記得:浮點數常數要存進 float 變數,就加 f;要存進 double,就可以省略
float expFloat = 1.39e-43f;
double expDouble = 47e47;
位元運算子(Bitwise Operators)整理包:^、|、&、~ 的直覺對照與實例
Java 中的位元運算子主要用於對整數型別(如 int
和 long
)的二進位位元進行操作:
運算子 | 說明 | 範例 | ||
---|---|---|---|---|
& | 位元 AND | 12 & 6 = 4 | ||
| | 位元 OR | 12 | 6 = 14 | ||
^ | 位元 XOR | 12 ^ 6 = 10 | ||
~ | 位元反轉 | ~12 = -13 |
範例:
// XOR: ^
// AND: &
// OR: |
// NOT: ~
// 定義變數
int a = 12; // 二進制表達為 1100
int b = 6; // 二進制表達為 0110
// XOR: ^ (異或)
int resultXor = a ^ b; // 結果是 10 (二進制為 1010)
// AND: & (與)
int resultAnd = a & b; // 結果是 4 (二進制為 0100)
// OR: | (或)
int resultOr = a | b; // 結果是 14 (二進制為 1110)
// NOT: ~ (非)
int resultNot = ~a; // -13,對 a 的每一位進行反轉
複合指定運算子
這些位元運算子也可以與 =
結合成指定運算子,讓程式更簡潔
a ^= b; // a 現在等於 10
a |= b; // a 現在等於 14
a &= b; // a 現在等於 4
注意:~
是一元運算子,不能與=
結合,不能寫成a ~= b
,那是違法語法
位移運算子(Shift Operators)完全圖解:<<、>>、>>> 差異 + 陷阱
位移運算子是 Java 中針對整數型別(如 int、long)進行位元操作的一種工具。簡單來說,它們能夠「把一串 0 和 1 數值往左或往右推」,就像搬磚頭一樣,只是你在搬的是數位的磚
常見的三種位移運算子
運算子 | 中文說法 | 行為說明 |
---|---|---|
<< | 左移 | 將位元往左推,右邊(低位)補 0,相當於數值 * 2 的 n 次方 |
>> | 有號右移 | 將位元往右推,保留符號位(最高位),負數補 1,正數補 0 |
>>> | 無號右移 | 將位元往右推,高位一律補 0,忽略正負號,常用於位元資料處理 |
它們到底在幹嘛?從實例來看
int x = 4; // 二進位: 00000000 00000000 00000000 00000100
int left = x << 1; // 左移一位:變成 00000000 00000000 00000000 00001000 -> 8
int y = -8; // 二進位: 11111111 11111111 11111111 11111000
int signed = y >> 1; // 有號右移:最高位補 1 -> 負數仍為負數
int unsigned = y >>> 1; // 無號右移:最高位補 0 -> 整數變大,可能變成很大的正數
那這東西有什麼用?
<<
左移一位 = 原數值 * 2;左移兩位 = * 4;以此類推。這對於做快速乘法或位元遮罩運算很常見(尤其在嵌入式、圖像處理、hash 算法等領域)>>
常用於將一個數值縮小(相當於除以 2),而且會保留符號,適合用在邏輯運算或補償負數的情況>>>
被稱為「無符號右移」,在處理二進位資料(例如加密、壓縮、Hash)時很重要,因為它完全忽略符號,能確保資料移動時不會因為負數而亂掉
常見踩雷區:byte 和 short 的自動升級
在 Java 中,小型整數像是 byte
和 short
在做位移時會自動升級為 int,這導致你可能會看到超奇怪的結果
byte b = -1;
System.out.println(Integer.toBinaryString(b)); // 11111111
System.out.println(Integer.toBinaryString(b >>> 2));
// 11111111111111111111111111111111
這是因為 Java 會先把 b
變成 int
(補滿 32 位元)再做位移,導致你以為的 8 位元其實已經擴充變形了。要避免這種錯誤,你應該手動轉型或改用 int
複合指定運算子(讓你寫得更短)
位移運算子也支援複合指定方式:
int a = 8;
a <<= 1; // 相當於 a = a << 1;結果是 16
支援的有:
<<=
>>=
>>>=
範例:
public class URShift {
public static void main(String[] args) {
int i = -1;
print(Integer.toBinaryString(i));
i >>>= 10;
print(Integer.toBinaryString(i));
long l = -1;
print(Long.toBinaryString(l));
l >>>= 10;
print(Long.toBinaryString(l));
short s = -1;
print(Integer.toBinaryString(s));
s >>>= 10;
print(Integer.toBinaryString(s));
byte b = -1;
print(Integer.toBinaryString(b));
b >>>= 10;
print(Integer.toBinaryString(b));
b = -1;
print(Integer.toBinaryString(b));
print(Integer.toBinaryString(b>>>10));
}
} /* Output:
11111111111111111111111111111111
1111111111111111111111
1111111111111111111111111111111111111111111111111111111111111111
111111111111111111111111111111111111111111111111111111
11111111111111111111111111111111
11111111111111111111111111111111
11111111111111111111111111111111
11111111111111111111111111111111
11111111111111111111111111111111
1111111111111111111111
*/
- 對於
int
型別,-1
的二進位表示為全1
- 使用無符號右移
>>>
後,高位補0
,結果位元數減少 - 對於
short
和byte
,在位移操作時被提升為int
,可能導致預期外的結果
結論
學會這些邏輯與位元運算子,並不只是為了寫出更簡潔的程式碼,它們是理解 Java 在底層如何運作、如何有效控制流程與效能的核心概念。從 &&
的短路判斷,到 ^
的異或操作,再到 >>>
的無號右移,每個看似冷門的符號背後都有實際應用場景
如果你曾經被 compiler 抱怨型別錯誤、在 debug 時不懂為什麼變數值突然爆掉,那麼這篇應該幫你補上了那些「本來只在考卷上出現」的概念
希望這份整理能成為你未來寫程式時的查表夥伴,或至少在你 Google java >>> 差別
時,能少花點時間看 StackOverflow 吵架
Comments ()