條件運算符 booleanExpr ? expr1 : expr2 是 Java 最常用的表達式之一,但其結果類型推導規則 (JLS 15.25) 極其精微,稍有不慎導致意外的類型提升、精度丟失、甚至編譯錯誤。本文通過標準案例、字節碼分析、JLS 規則拆解,徹底講透這個「陣痛」來源。
char x = 'A'; // ASCII 65
int i = 0;
System.out.println(true ? x : 0); // 輸出: 'A' (char)
System.out.println(true ? x : 1111111110); // 輸出: 65 (int)
System.out.println(false ? i : x); // 輸出: 65 (int)
關鍵在於 expr1 和 expr2 的類型如何統一。Java 必須在編譯期確定整個條件表達式的靜態類型,這決定了輸出流選擇哪個 println 重載版本。
條件運算符結果類型按優先級確定:
int a = 1, b = 2;
int result = true ? a : b; // 類型 int
char c = 'A';
int zero = 0; // 編譯期常量,值 0 在 char 範圍內 (0-65535)
char result = true ? c : zero; // 類型 char!
案例 1 解析:true ? x : 0 → x 是 char,0 是 int 常量且值在 char 範圍 → 結果類型 char → println(char) 輸出 'A'。
char c = 'A';
int big = 1111111110; // 超出 char 最大值 65535
int result = true ? c : big; // 類型 int!
案例 2 解析:true ? x : 1111111110 → 常量超出 char 範圍 → char 提升為 int → 結果類型 int → println(int) 輸出 65。
int i = 0; // 變量,非常量
char c = 'A';
int result = false ? i : c; // 類型 int (char 提升為 int)
案例 3 解析:false ? i : x → i 是 int 變量 (非常量表達式),x 是 char → 不滿足規則 2 → 數值提升 char → int → 結果 int → 輸出 65。
class Test {}
Test t = false ? new Object() : new Test(); // 編譯錯誤!
// expr1: Object, expr2: Test → 共同父類 Object
// 結果類型 Object,不能賦值給 Test 變量
修正:Object t = false ? new Object() : new Test(); 或強制轉型。
// Case 1: true ? x : 0
0: iconst_1
1: ifeq 10
4: bipush 65 // 'A' = 65
6: istore_1
7: goto 13
10: iconst_0
11: istore_1
13: getstatic System.out
16: iload_1
17: invokevirtual PrintStream.println(C)V // 關鍵:println(char)
// Case 2: true ? x : 1111111110
13: iload_1
14: invokevirtual PrintStream.println(I)V // 關鍵:println(int)
// Case 3: false ? i : x
13: iload_1
14: invokevirtual PrintStream.println(I)V // 關鍵:println(int)
字節碼鐵證:編譯器根據靜態類型選擇不同重載方法,運行時無任何類型判斷邏輯。
Integer a = 100;
int b = 200;
var result = true ? a : b; // 結果類型 int (自動拆箱),可能拋 NPE 若 a 為 null
// 建議:統一用包裝類或基本類型
Integer result = true ? a : Integer.valueOf(b);
char c = '中'; // Unicode 20013
String s = true ? String.valueOf(c) : "fallback"; // 明確轉 String,避免歧義
// 錯誤
List list = condition ? new ArrayList<>() : new LinkedList<>();
// 編譯器推導為 ArrayList | LinkedList 共同父類 List,但泛型擦除後可能有未檢查警告
// 正確
List list = condition ? new ArrayList<>() : new LinkedList<>(); // JDK 8+ 目標類型推導可工作
// 或顯式
List list = condition ? (List) new ArrayList<>() : new LinkedList<>();
int x = a > 0 ? 1 : -1; // 正確
int y = a > 0 ? 1 : -1 + 2; // 陷阱:實為 a>0 ? 1 : (-1+2) = 1
// 建議
int y = a > 0 ? 1 : (-1 + 2);
expr1 類型 T1, expr2 類型 T2
│
├─ T1 == T2 → 結果 T1
│
├─ T1/T2 為 byte/short/char,另為 int 常量表達式
│ ├─ 常量值在窄類型範圍內 → 結果 窄類型 (byte/short/char)
│ └─ 常量值超出範圍 → 結果 int
│
├─ T1/T2 為基本數值類型 → 雙向數值提升 (byte/short/char→int→long→float→double)
│
├─ T1/T2 為 boolean/Boolean → 結果 boolean (自動拆箱)
│
└─ T1/T2 為引用類型
├─ 其中一為 null → 結果另一類型
└─ 找最近公共父類 (LUB) → 結果 LUB
(注意:泛型類型參數不參與 LUB 計算,僅原始類型)
條件運算符的類型推導規則是 Java 語言規範中最「聰明」也最「危險」的角落之一。它試圖在表達式層面做「正確的事」(保留窄類型、避免顯式轉型),卻常因常量/變量、範圍邊界、泛型擦除等細節產生違背直覺的結果。作為開發者,**明確類型意圖、善用顯式轉型、避免混用基本/包裝類型**,比依賴編譯器推導更安全、更可讀、更易維護。