Java自动装箱拆箱

372 阅读2分钟

准备

  1. jdk1.8
  2. idea
  3. CFR反编译插件

安装插件

  1. 点击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()方法。

通过分析以下表格

基础类型包装类型装箱方法拆箱方法
charCharacterCharacter.valueOf((char)'a')charA.charValue()
booleanBooleanBoolean.valueOf((boolean)true)flagA.booleanValue()
byteByteByte.valueOf((byte)0)byteA.byteValue()
shortShortShort.valueOf((short)0)shortA.shortValue()
intIntegerInteger.valueOf((int)1)i.intValue()
longLongLong.valueOf((long)0L)longA.longValue()
floatFloatFloat.valueOf((float)0.0f)floatA.floatValue()
doubleDoubleDouble.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 循环中有大量拆装箱操作,会浪费很多资源。

参考资料