自动装箱与拆箱

面试官问:"什么是自动装箱和拆箱?"

候选人小熊答:"自动装箱是 int 转 Integer,拆箱是 Integer 转 int。"

面试官追问:"自动装箱发生在什么时候?"

小熊说:"在需要的时候?"

面试官又问:"下面这段代码会输出什么?"

Integer a = 1;
Integer b = 2;
Integer c = 3;
Integer d = 3;
Integer e = 128;
Integer f = 128;

System.out.println(c == d);
System.out.println(e == f);

小熊说:"c==d 是 true,e==f 是 false?"

面试官:"为什么?"

小熊答不上来。

【面试官心理】 这道题考查的是候选人对自动装箱和 Integer 缓存池联动关系的理解。能说出"自动装箱调用 valueOf"和"缓存范围"的候选人,说明理解了 Java 的装箱机制。

一、什么是自动装箱/拆箱 🔴

1.1 定义

// 自动装箱(Auto Boxing):基本类型 → 包装类型
Integer a = 100; // 等价于 Integer a = Integer.valueOf(100);

// 自动拆箱(Auto Unboxing):包装类型 → 基本类型
int b = a; // 等价于 int b = a.intValue();

1.2 编译器层面的转换

// 源代码
Integer a = 100;
int b = a + 1;

// 编译器自动转换为
Integer a = Integer.valueOf(100);
int b = a.intValue() + 1;

二、自动装箱的源码 🔴

// Integer.valueOf()
public static Integer valueOf(int i) {
    if (i >= IntegerCache.low && i <= IntegerCache.high)
        return IntegerCache.cache[i + (-IntegerCache.low)];
    return new Integer(i);
}

// 关键:i >= -128 && i <= 127 时,返回缓存对象
// 否则创建新对象

三、自动装箱与缓存池的联动 🔴

3.1 缓存范围内的比较

Integer c = 127;
Integer d = 127;
c == d; // true

// 编译器展开为:
Integer c = Integer.valueOf(127); // 从缓存池取
Integer d = Integer.valueOf(127); // 从缓存池取
c == d; // true(同一个对象)

3.2 超出缓存范围的比较

Integer e = 128;
Integer f = 128;
e == f; // false

// 编译器展开为:
Integer e = Integer.valueOf(128); // new Integer(128)
Integer f = Integer.valueOf(128); // new Integer(128)
e == f; // false(两个不同的对象)

四、自动装箱的性能陷阱 🔴

4.1 循环中的自动装箱

// ❌ 糟糕:每次循环都自动装箱,产生大量对象
long sum = 0;
for (int i = 0; i < 1000000; i++) {
    Long sumObj = sum; // 自动装箱:long → Long
    sum = sumObj;     // 自动拆箱:Long → long
}

// 产生了 200 万个 Long 对象!

// ✅ 正确:使用基本类型
long sum = 0;
for (int i = 0; i < 1000000; i++) {
    sum += i; // 无装箱
}

4.2 HashMap 的性能问题

// ❌ 糟糕:大量自动装箱
Map<Integer, Long> cache = new HashMap<>();
for (int i = 0; i < 1000000; i++) {
    cache.put(i, (long) i); // 自动装箱两次:int→Integer, long→Long
}

// ✅ 正确:使用基本类型的 Map(如果 JDK 支持)
// JDK 17+ 可以用 Map.of()
// 或者考虑其他缓存方案

五、常见陷阱 🟡

5.1 null 与拆箱

// ❌ 危险:拆箱 null
Integer a = null;
int b = a; // 自动拆箱:a.intValue()
// 抛出 NullPointerException!

// ✅ 正确:先判空
if (a != null) {
    int b = a; // 安全拆箱
}

5.2 算术运算中的自动拆箱

// 自动拆箱后进行算术运算
Integer a = 100;
Integer b = 200;
int sum = a + b; // a.intValue() + b.intValue()

// += 操作符也会自动拆箱
a += 1; // a = Integer.valueOf(a.intValue() + 1)

5.3 equals 比较中的自动装箱

// 两个 Integer 比较
Integer a = 100;
Integer b = 100;
a.equals(b); // true(比较值)

// 不同类型比较
Integer a = 100;
int b = 100;
a.equals(b); // true(equals 参数是 Object,会自动装箱 b)

// 自动装箱比较
a == b; // true(== 时自动拆箱 a)

六、生产避坑 🟡

6.1 三元运算符的陷阱

// ❌ 危险
Boolean flag = null;
boolean result = flag ? true : false; // flag 自动拆箱 → NPE

// ✅ 正确
boolean result = (flag != null) && flag; // 先判空

6.2 方法参数的类型匹配

void print(int num) {
    System.out.println(num);
}

Integer num = 100;
print(num); // 自动拆箱:num.intValue()

七、追问升级

面试官:"为什么自动装箱要用 valueOf 而不是 new?"

// 如果 Integer.valueOf() 使用 new:
// Integer a = 127; // new Integer(127)
// Integer b = 127; // new Integer(127)
// a == b; // false(两个不同的对象)

// valueOf() 使用缓存:
// Integer a = 127; // IntegerCache 取
// Integer b = 127; // IntegerCache 取
// a == b; // true(同一个对象)

// 结论:使用 valueOf 可以复用缓存对象,节省内存,提高性能
// 这是 Java 1.5 引入自动装箱时的重要优化

【面试官心理】 能说出"缓存复用"和"节省内存"两个原因的候选人,说明理解了 Java 的性能优化机制。这是 P6 的要求。