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