JDK 5 引入自動裝箱與自動拆箱,讓基本類型與包裝類無縫轉換,但背後隱藏性能陷阱與邏輯 Bug。本文從字節碼層面還原裝箱/拆箱調用鏈,剖析IntegerCache 緩存邊界、== 比較陷阱、空指針風險、運算符行為,給出生產環境避坑指南。
// 源碼
Integer i = 100; // 自動裝箱
int j = i; // 自動拆箱
// 反編譯字節碼 (javap -c)
0: bipush 100
2: invokestatic #2 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
5: astore_1
6: aload_1
7: invokevirtual #3 // Method java/lang/Integer.intValue:()I
10: istore_2
Integer.valueOf(int)Integer.intValue()public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
// IntegerCache 靜態初始化
static { /* 緩存 -128 ~ 127 */ }
結論:-128 ~ 127 之間的整數裝箱返回緩存對象,超出範圍每次 new 新對象。
Integer a = 100, b = 100; // true (緩存)
Integer c = 200, d = 200; // false (新對象)
Integer e = 200;
int f = 200;
System.out.println(e == f); // true (e 自動拆箱為 int 比較值)
規則:== 比較引用(包裝類)或值(基本類型)。包裝類與基本類型比較 → 包裝類拆箱。
Integer obj = null;
int val = obj; // 編譯通過,運行拋 NPE (obj.intValue())
防範:拆箱前判空,或使用 Objects.requireNonNull、Optional。
Integer x = 100, y = 200;
Long z = 300L;
System.out.println(x + y); // 300 (雙方拆箱相加 → int → 裝箱 Integer → toString)
System.out.println(z == x + y); // true (x+y 拆箱為 int,z 拆箱為 long,數值比較)
System.out.println(z.equals(x+y)); // false (z.equals 接收 Object,x+y 裝箱為 Integer,類型不同)
規則:算術運算符(+ - * /)觸發雙向拆箱,結果為基本類型,再根據上下文裝箱。
Long sum = 0L;
for (long i = 0; i < 1_000_000_000; i++) {
sum += i; // 每次迭代:sum 拆箱 → 相加 → 裝箱新 Long → GC 壓力巨大
}
修正:使用基本類型 long sum = 0L; 或 LongAdder。
| 表達式 | 結果 | 說明 |
|---|---|---|
| new Integer(100) == new Integer(100) | false | 不同對象引用 |
| Integer.valueOf(100) == Integer.valueOf(100) | true | 緩存同一對象 |
| Integer.valueOf(100).equals(100) | true | 100 裝箱 → Integer,equals 比較值 |
| Integer.valueOf(100).equals(Long.valueOf(100)) | false | equals 先判類型,Integer != Long |
a.equals(b),除非確定在緩存範圍內自動裝箱/拆箱是語法糖,本質是編譯器插入 valueOf/xxxValue 調用。掌握 IntegerCache -128~127 緩存邊界、== 比較引用 vs equals 比較值、運算符觸發雙向拆箱、null 拆箱拋 NPE 四大核心規則,配合基本類型優先原則,即可寫出正確高效的 Java 代碼。