准备
- jdk1.8
- idea
- CFR反编译插件
安装插件
- 点击idea->Preferences->Plugins,在搜索框输入
CFR Decompile关键字,下载CFR Decompile插件。
自动装箱与拆箱
自动拆箱装箱代码
public void boxingAndUnboxing() {
Integer i = 1; // 自动装箱
int j = i; // 自动拆箱
}
选中代码文件,右键选择show bytecode outline,反编译得到下面的结果。
public void boxingAndUnboxing() {
Integer i = Integer.valueOf((int)1);
int j = i.intValue();
}
通过对比源码和反编译后的代码,可以发现int的自动装箱是通过Integer.valueOf((int)1)方法,Integer的自动拆箱是通过i.intValue()方法。
通过分析以下表格
| 基础类型 | 包装类型 | 装箱方法 | 拆箱方法 |
|---|---|---|---|
| char | Character | Character.valueOf((char)'a') | charA.charValue() |
| boolean | Boolean | Boolean.valueOf((boolean)true) | flagA.booleanValue() |
| byte | Byte | Byte.valueOf((byte)0) | byteA.byteValue() |
| short | Short | Short.valueOf((short)0) | shortA.shortValue() |
| int | Integer | Integer.valueOf((int)1) | i.intValue() |
| long | Long | Long.valueOf((long)0L) | longA.longValue() |
| float | Float | Float.valueOf((float)0.0f) | floatA.floatValue() |
| double | Double | Double.valueOf((double)0.0) | doubleA.doubleValue() |
可以得出结论:
自动装箱都是通过包装类的
valueOf()方法来实现的.自动拆箱都是通过包装类对象的xxxValue()来实现的。
装箱与拆箱场景
前面的变量初始化和赋值也会自动装箱拆箱,这里就不介绍了。
场景一、将基本数据类型放入集合类
集合中只能接受对象类型数据,但是往list中添加int类型却不会报错。
List<Integer> list = new ArrayList<>();
for (int i = 0; i< 10; i++) {
list.add(i);
}
将代码反编译后得到如下代码。
ArrayList<Integer> list = new ArrayList<Integer>();
for (int i = 0; i < 10; ++i) {
list.add(Integer.valueOf((int)i));
}
由此可以得出结论,我们把基本数据类型放入集合中会自动进行装箱操作。
场景二、包装类型和基本类型进行比较
Integer a = 1;
System.out.println(a == 1 ? "等于" : "不等于");
Boolean b = false;
System.out.println(b == false ? "等于" : "不等于");
代码反编译后
Integer a = Integer.valueOf((int)1);
System.out.println((String)(a.intValue() == 1 ? "\u7b49\u4e8e" : "\u4e0d\u7b49\u4e8e"));
Boolean b = Boolean.valueOf((boolean)false);
System.out.println((String)(!b.booleanValue() ? "\u7b49\u4e8e" : "\u4e0d\u7b49\u4e8e"));
}
由上可以看到对象与基本类型进行比较时,实际是将包装类型进行拆箱成基本数据类型在进行比较。
场景三、包装类型的运算
Integer integerA = 0;
Integer integerB = 1;
System.out.println(integerA+integerB);
代码反编译后
Integer integerA = Integer.valueOf((int)0);
Integer integerB = Integer.valueOf((int)1);
System.out.println((int)(integerA.intValue() + integerB.intValue()));
由此可以看到包装类型在进行四则运算时,先自动拆箱成基本数据类型在进行四则运算。
场景四、 三目运算符的使用
boolean flag = true;
Integer i=null;
int j = 1;
int k = flag ? i : j;
上述代码运行后会抛出空指针异常。
代码反编译后
boolean flag = true;
Integer i = null;
int j = 1;
int k = flag ? i.intValue() : j;
不难发现其中包装类型进行了自动拆箱操作,而此时的i为null,难怪会抛出空指针异常。
场景五、 方法参数与返回值
public int unboxing(Integer i) {
return i;
}
public Integer boxing(int i) {
return i;
}
代码反编译后
public int unboxing(Integer i) {
return i.intValue();
}
public Integer boxing(int i) {
return Integer.valueOf((int)i);
}
自动装箱拆箱的缓存
Java SE 的自动拆装箱还提供了一个和缓存有关的功能,我们先来看以下代码,猜测一下输出结果:
Integer integer1 = 3;
Integer integer2 = 3;
if (integer1 == integer2)
System.out.println("integer1 == integer2");
else
System.out.println("integer1 != integer2");
Integer integer3 = 300;
Integer integer4 = 300;
if (integer3 == integer4)
System.out.println("integer3 == integer4");
else
System.out.println("integer3 != integer4");
我们普遍任务上面连个比较都是false,因为上面是两个对象使用==进行比较,虽然值相等,但是==比较的是不是同一个对象。上面不是
同一个对象,所以应该两个都是false。
但实际运行结果却如下:
integer1 == integer2
integer3 != integer4
这个就是因为Integer的缓存机制,在 Java 5 中,在 Integer 的操作上引入了一个新功能来节省内存和提高性能。整型对象通过使用相同的对象引用实现了缓存和重用。
适用于整数值区间 -128 至 +127。
只适用于自动装箱。使用构造函数创建对象不适用。
自动装箱拆箱带来的问题
当然,自动拆装箱是一个很好的功能,大大节省了开发人员的精力,不再需要关心到底什么时候需要拆装箱。但是,他也会引入一些问题。
包装对象的数值比较,不能简单的使用
==,虽然 -128 到 127 之间的数字可以,但是这个范围之外还是需要使用equals比较。前面提到,有些场景会进行自动拆装箱,同时也说过,由于自动拆箱,如果包装类对象为 null ,那么自动拆箱时就有可能抛出 NPE。
如果一个 for 循环中有大量拆装箱操作,会浪费很多资源。