& 和 && 均為邏輯 AND 運算符,但求值策略本質不同:&& 具備短路特性,& 為全評估。本文通過字節碼、執行流程、副作用陷阱三個維度深度對比,並給出選用原則。
| 運算符 | 名稱 | 操作數類型 | 結果類型 |
|---|---|---|---|
| & | 邏輯 AND / 位 AND | boolean / 整數 | boolean / 整數 |
| && | 條件 AND (短路) | boolean | boolean |
關鍵:& 用於 boolean 時為邏輯 AND,用於整數時為按位 AND;&& 僅支持 boolean。
// 測試 & 全評估
public static void testAnd() {
int i = 3;
if ((i++ > 5) & (i++ < 9)) { // 兩個條件都執行
System.out.println("if body");
}
System.out.println("final i = " + i); // 輸出 5
}
// 測試 && 短路
public static void testShortCircuit() {
int i = 3;
if ((i++ > 5) && (i++ < 9)) { // 第一個 false,第二個不執行
System.out.println("if body");
}
System.out.println("final i = " + i); // 輸出 4
}
| 步驟 | & (全評估) | && (短路) |
|---|---|---|
| 初始 i | 3 | 3 |
評估左側 i++ > 5 | 3>5=false, i→4 | 3>5=false, i→4 |
| 是否評估右側 | 是 | 否 (短路) |
評估右側 i++ < 9 | 4<9=true, i→5 | 跳過 |
| if 條件結果 | false & true = false | false |
| 最終 i | 5 | 4 |
// & 版本關鍵字節碼
iload_1 // load i
iinc 1, 1 // i++
iconst_5
if_icmpgt L1 // >5 跳轉
iconst_0 // false
goto L2
L1: iconst_1 // true
L2: iload_1 // load i (再次)
iinc 1, 1 // i++ (第二次自增)
bipush 9
if_icmplt L3 // <9 跳轉
iconst_0
goto L4
L3: iconst_1
L4: iand // 邏輯 AND (位運算)
ifeq L5 // false 跳過 if 體
// && 版本關鍵字節碼
iload_1
iinc 1, 1
iconst_5
if_icmpgt L1
iconst_0
goto L2
L1: iconst_1
L2: ifeq L3 // 關鍵:左側 false 直接跳轉到 L3 (跳過右側評估)
... 右側評估代碼 ...
L3: ifeq L4
... if 體 ...
核心差異:&& 編譯為兩次條件跳轉,左側 false 直接跳過右側代碼塊;& 編譯為完整評估雙方再 iand。
boolean checkAndLog(String label) {
System.out.println("Checking " + label);
return true;
}
// 使用 &
if (checkAndLog("A") & checkAndLog("B")) { ... }
// 輸出: Checking A / Checking B (兩個都執行)
// 使用 &&
if (checkAndLog("A") && checkAndLog("B")) { ... }
// 輸出: Checking A (B 不執行)
String str = null;
// 安全:短路防止 NPE
if (str != null && str.length() > 0) { ... }
// 危險:全評估導致 NPE
if (str != null & str.length() > 0) { ... } // 拋出 NullPointerException
// 短路模式:檢查並獲取
if ((resource = pool.acquire()) != null && resource.isValid()) {
use(resource);
}
// 全評估模式:即使獲取失敗也會執行 isValid() → NPE
if ((resource = pool.acquire()) != null & resource.isValid()) { ... }
// 權限掩碼檢查
int permission = user.getPermission();
if ((permission & PERMISSION_WRITE) != 0) { // 檢查寫權限位
allowWrite();
}
// 奇偶判斷
if ((num & 1) == 1) { isOdd(); }
// 清除標誌位
flags &= ~FLAG_DEBUG;
// 快速取模 (2的冪次)
int mod = value & (size - 1); // 等價 value % size, size為2的冪
| 場景 | 推薦運算符 | 理由 |
|---|---|---|
| 邏輯判斷、條件檢查 | && | 短路提升性能、防護 NPE、避免副作用 |
| 需要所有條件副作用執行 | & | 確保所有方法調用/賦值執行 |
| 位掩碼、權限位操作 | & | 按位 AND 語義 |
| 數學運算優化 | & | 位運算替代取模/除法 |
| 代碼可讀性 | && | 主流習慣,意圖清晰 |
// 輸出什麼?
int x = 0;
if (true & (x++ > 0)) System.out.println("A");
System.out.println(x); // 輸出 1
int y = 0;
if (true && (y++ > 0)) System.out.println("B");
System.out.println(y); // 輸出 0 (短路,y++ 不執行)