自动装拆箱
核心概念
自动装箱(Autoboxing):基本类型 → 包装类型 自动拆箱(Unboxing):包装类型 → 基本类型
这是Java编译器提供的语法糖,让基本类型和包装类之间可以无缝转换。
一、底层原理
1.1 装箱:valueOf()
// 源码
Integer num = 100;
// 编译器自动转换为
Integer num = Integer.valueOf(100);
valueOf()方法实现:
public static Integer valueOf(int i) {
// 范围内直接返回缓存对象
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
// 超出范围创建新对象
return new Integer(i);
}
1.2 拆箱:xxxValue()
// 源码
Integer num = 100;
int x = num;
// 编译器自动转换为
int x = num.intValue();
intValue()方法实现:
public int intValue() {
return value; // 直接返回内部存储的int值
}
二、自动装拆箱的触发场景
2.1 装箱场景
① 赋值操作
Integer a = 10; // 自动装箱:Integer.valueOf(10)
② 方法参数
public void print(Integer num) { }
print(20); // 自动装箱:print(Integer.valueOf(20))
③ 集合操作
List<Integer> list = new ArrayList<>();
list.add(30); // 自动装箱:list.add(Integer.valueOf(30))
④ 三元表达式
Integer result = true ? 100 : 200.5;
// 100先装箱为Integer,再拆箱为int,最后转为double,再装箱为Double
// 实际结果类型是Number(Integer和Double的公共父类)
2.2 拆箱场景
① 赋值操作
Integer a = 10;
int b = a; // 自动拆箱:a.intValue()
② 运算操作
Integer a = 10;
Integer b = 20;
int sum = a + b; // 自动拆箱:a.intValue() + b.intValue()
③ 比较操作
Integer a = 10;
if (a > 5) { } // 自动拆箱:a.intValue() > 5
④ 方法返回
public int getValue() {
Integer num = 100;
return num; // 自动拆箱:return num.intValue()
}
三、核心陷阱与解决方案
3.1 空指针异常(NPE)⚠️
陷阱代码
Integer num = null;
int x = num; // 💥 NullPointerException
// 原因:编译后变为
int x = num.intValue(); // null.intValue() 报错
真实场景
// 数据库查询结果可能为null
public void calculateBonus(Integer userId) {
Integer bonus = userDao.getBonus(userId); // 可能返回null
int total = bonus + 100; // 💥 NPE
}
解决方案
// 方案1:null检查
Integer bonus = userDao.getBonus(userId);
int total = (bonus != null ? bonus : 0) + 100;
// 方案2:使用包装类运算(会自动装箱)
Integer total = bonus + 100; // bonus为null时,运算前会报错,提前发现问题
// 方案3:Optional(推荐)
int total = Optional.ofNullable(userDao.getBonus(userId))
.orElse(0) + 100;
3.2 缓存池引发的 == 判断错误
// ✅ 缓存范围内(-128~127)
Integer a = 100;
Integer b = 100;
System.out.println(a == b); // true(共享同一个对象)
// ❌ 超出缓存范围
Integer x = 200;
Integer y = 200;
System.out.println(x == y); // false(两个不同对象)
正确做法:
// 包装类比较,永远用equals()
Integer a = 200;
Integer b = 200;
System.out.println(a.equals(b)); // true
3.3 性能问题:频繁装拆箱
问题代码
// ❌ 每次循环都装箱一次,创建10000个Integer对象
Long sum = 0L;
for (int i = 0; i < 10000; i++) {
sum += i; // sum.longValue() + i,结果再装箱为Long
}
优化方案
// ✅ 使用基本类型
long sum = 0L;
for (int i = 0; i < 10000; i++) {
sum += i; // 纯基本类型运算,无装箱开销
}
性能对比
// 测试:累加1000万次
// 包装类版本:耗时约800ms,创建1000万个对象,触发多次GC
// 基本类型版本:耗时约20ms,无对象创建
四、equals() vs ==
4.1 核心区别
| 比较方式 | 基本类型 | 包装类型 |
|---|---|---|
| == | 比较值 | 比较引用(地址) |
equals() |
不支持 | 比较值(重写后) |
4.2 典型案例
// 案例1:缓存池内
Integer a = 127;
Integer b = 127;
System.out.println(a == b); // true(同一对象)
System.out.println(a.equals(b)); // true(值相等)
// 案例2:缓存池外
Integer x = 200;
Integer y = 200;
System.out.println(x == y); // false(不同对象)
System.out.println(x.equals(y)); // true(值相等)
// 案例3:混合比较
Integer m = 100;
int n = 100;
System.out.println(m == n); // true(m自动拆箱后比较值)
记忆法则:
包装类比较,永远用equals() 除非明确要判断"是否同一对象"(如单例模式)
五、高频面试题
Q1: 什么是自动装箱和自动拆箱?底层如何实现?
答:
- 装箱:
int → Integer,编译器调用Integer.valueOf() - 拆箱:
Integer → int,编译器调用intValue() - 本质:编译器的语法糖,简化代码编写
Q2: 自动拆箱时为什么可能出现空指针异常?
答:
Integer num = null;
int x = num; // 拆箱调用num.intValue(),null.xxx()报NPE
避免方式:拆箱前检查null,或使用Optional
Q3: 为什么频繁装拆箱会影响性能?该如何优化?
答:
- 问题:每次装箱都创建新对象(超出缓存范围),增加GC压力
- 优化:
- 高频运算使用基本类型
- 预估容量,减少集合扩容导致的装箱
- 使用
@Primitive注解(Lombok)或原始类型集合(Trove/Eclipse Collections)
Q4: Integer a = 128, b = 128,a == b 为 false 的原因?
答:
- 128超出缓存池范围(-128~127)
- 两次装箱创建了两个不同的对象
- ==比较的是引用地址,不同对象返回false
- 正确写法:
a.equals(b)
Q5: Integer.valueOf(127) == Integer.valueOf(127) 为什么为 true?
答:
- 127在缓存池范围内
valueOf()返回的是缓存池中的同一个对象- ==比较引用,指向同一对象返回true
延伸:缓存池详见 03-缓存池机制(Cache Pool)
Q6: == 和 equals 在比较 Integer 对象时有何区别?
答:
| 方法 | 比较内容 | 使用场景 |
|---|---|---|
| == | 对象引用(地址) | 判断是否同一对象 |
equals() |
对象的值 | 判断值是否相等 |
推荐:包装类比较统一用equals()
六、最佳实践
✅ 推荐做法
// 1. 基本类型优先
public int calculate(int a, int b) {
return a + b;
}
// 2. 包装类比较用equals()
Integer a = 200;
Integer b = 200;
if (a.equals(b)) { }
// 3. 空值检查
Integer num = getFromDatabase();
int value = (num != null) ? num : 0;
// 4. 避免循环装箱
long sum = 0L; // 而不是Long sum = 0L
❌ 避免做法
// 1. 循环中装箱
Long sum = 0L;
for (int i = 0; i < 10000; i++) {
sum += i; // 每次都装箱
}
// 2. 包装类用 ==
Integer a = 200;
Integer b = 200;
if (a == b) { } // 错误,应该用equals()
// 3. 忽略null检查
Integer num = null;
int x = num; // NPE
相关链接
- 01-基本类型与包装类 - 8种基本类型和包装类对应关系
- 03-缓存池机制(Cache Pool) - Integer缓存池原理与实现
- HashMap原理 - equals()和hashCode()在集合中的应用
记忆口诀
装箱拆箱编译糖,valueOf和intValue 缓存池内同对象,超出范围各自造 包装比较用equals,基本类型用双等 空值拆箱抛NPE,循环装箱性能糟
⬅️ 基本类型与包装类 🏠 00-Java ➡️ 缓存池机制(Cache Pool)
💬 评论