Java装箱与拆箱完全指南:从原理到实战
刚学Java那会儿,看到int和Integer总觉得困惑:为啥有了int还要有Integer?后来才明白,这背后涉及到Java的一个重要机制——装箱和拆箱。这个知识点看似简单,但里面的坑可不少!今天就来彻底搞懂它。
第一章:基本类型 vs 包装类型
1.1 Java的两套类型系统
Java有两套类型系统,这让很多初学者感到困惑:
// 基本类型(Primitive Types)
int num1 = 10;
double price = 99.9;
boolean flag = true;
char ch = 'A';// 包装类型(Wrapper Types)
Integer num2 = 10;
Double price2 = 99.9;
Boolean flag2 = true;
Character ch2 = 'A';
8种基本类型和对应的包装类:
| 基本类型 | 包装类型 | 字节数 | 取值范围 |
|---|---|---|---|
| byte | Byte | 1 | -128 ~ 127 |
| short | Short | 2 | -32768 ~ 32767 |
| int | Integer | 4 | -2^31 ~ 2^31-1 |
| long | Long | 8 | -2^63 ~ 2^63-1 |
| float | Float | 4 | 约±3.4E38 |
| double | Double | 8 | 约±1.7E308 |
| char | Character | 2 | 0 ~ 65535 |
| boolean | Boolean | - | true / false |
1.2 为什么需要两套类型?
你可能会问:有了基本类型,为啥还要包装类型?
基本类型的特点:
- ✅ 性能高:直接存储值,不需要额外对象开销
- ✅ 简单高效:适合大量计算
- ❌ 不是对象:不能用在需要对象的地方(如集合)
- ❌ 没有方法:不能像对象一样调用方法
- ❌ 不能为null:无法表示"没有值"
包装类型的特点:
- ✅ 是对象:可以用在集合、泛型等场景
- ✅ 有方法:提供各种实用方法
- ✅ 可以为null:能表示"没有值"的状态
- ❌ 性能开销:需要创建对象,占用更多内存
1.3 实际场景对比
// 场景1:简单计算 - 用基本类型
int sum = 0;
for (int i = 0; i < 1000; i++) {sum += i; // 快速、高效
}// 场景2:集合 - 必须用包装类型
List<Integer> numbers = new ArrayList<>();
numbers.add(100); // 这里其实发生了装箱// 场景3:可能为空的情况 - 用包装类型
Integer age = getUserAge(); // 可能返回null
if (age != null && age > 18) {System.out.println("成年人");
}// 场景4:方法的便利性 - 包装类型
String numStr = "123";
int num = Integer.parseInt(numStr); // Integer提供的工具方法
String binary = Integer.toBinaryString(10); // 转二进制
第二章:装箱与拆箱的本质
2.1 什么是装箱和拆箱?
装箱(Boxing):基本类型 → 包装类型
// 手动装箱(以前的做法)
int num = 10;
Integer obj = new Integer(num); // 已过时,不推荐// 更好的做法
Integer obj2 = Integer.valueOf(num);
拆箱(Unboxing):包装类型 → 基本类型
// 拆箱
Integer obj = 100;
int num = obj.intValue(); // 手动拆箱
2.2 自动装箱和自动拆箱
Java 5引入了自动装箱/拆箱机制,编译器会自动帮我们转换:
// 自动装箱
Integer num = 10; // 编译器自动转换为 Integer.valueOf(10)// 自动拆箱
int value = num; // 编译器自动转换为 num.intValue()// 更复杂的例子
Integer a = 100;
Integer b = 200;
Integer c = a + b; // 1. a和b自动拆箱成int// 2. 相加得到int结果// 3. 结果自动装箱成Integer
编译器做了什么?
// 你写的代码
Integer num = 10;
int value = num;// 编译器实际生成的代码
Integer num = Integer.valueOf(10);
int value = num.intValue();
2.3 实际运行示例
public class BoxingExample {public static void main(String[] args) {// 自动装箱示例System.out.println("=== 自动装箱 ===");Integer a = 100; // 自动装箱System.out.println("装箱后:" + a);System.out.println("类型:" + a.getClass().getName());// 自动拆箱示例System.out.println("\n=== 自动拆箱 ===");int b = a; // 自动拆箱System.out.println("拆箱后:" + b);// 运算中的装箱拆箱System.out.println("\n=== 运算中的转换 ===");Integer x = 10;Integer y = 20;Integer z = x + y; // x和y拆箱、相加、结果装箱System.out.println("结果:" + z);// 集合中的装箱System.out.println("\n=== 集合中的装箱 ===");List<Integer> list = new ArrayList<>();list.add(1); // 1自动装箱成Integerlist.add(2);int sum = list.get(0) + list.get(1); // 自动拆箱、相加System.out.println("和:" + sum);}
}
输出:
=== 自动装箱 ===
装箱后:100
类型:java.lang.Integer=== 自动拆箱 ===
拆箱后:100=== 运算中的转换 ===
结果:30=== 集合中的装箱 ===
和:3
第三章:缓存机制——Integer的秘密
3.1 Integer缓存池
Integer有一个让人意外的特性:缓存!
public class IntegerCacheDemo {public static void main(String[] args) {// 小数字(-128到127)Integer a1 = 100;Integer a2 = 100;System.out.println("a1 == a2: " + (a1 == a2)); // true!// 大数字(超出缓存范围)Integer b1 = 200;Integer b2 = 200;System.out.println("b1 == b2: " + (b1 == b2)); // false!// 为什么会这样?System.out.println("\n对象地址:");System.out.println("a1: " + System.identityHashCode(a1));System.out.println("a2: " + System.identityHashCode(a2)); // 相同!System.out.println("b1: " + System.identityHashCode(b1));System.out.println("b2: " + System.identityHashCode(b2)); // 不同!}
}
输出:
a1 == a2: true
b1 == b2: false对象地址:
a1: 460141958
a2: 460141958
b1: 1163157884
b2: 1956725890
原理解析:
Integer内部有一个缓存池,默认缓存-128到127之间的对象:
// Integer类的部分源码(简化版)
public static Integer valueOf(int i) {// 如果在缓存范围内,直接返回缓存的对象if (i >= IntegerCache.low && i <= IntegerCache.high)return IntegerCache.cache[i + (-IntegerCache.low)];// 否则创建新对象return new Integer(i);
}// 缓存池
private static class IntegerCache {static final int low = -128;static final int high = 127; // 可通过JVM参数调整static fin