Java 的自动装箱(autoboxing)与拆箱(unboxing)

自动装箱(autoboxing)是指 Java 编译器自动将基本数据类型值转换成对应的包装类的对象,例如将 int 转换为 Integer 对象,将 boolean 转换问 Boolean 对象。而拆箱(unboxing)则是反过来转换。

基本数据类型及其包装类的对应

以下这些基本数据类型都有对应的包装类

基本数据类型 包装类
boolean Boolean
byte Byte
char Character
float Float
int Integer
long Long
short Short
double Double

自动装箱与拆箱的例子

自动装箱和拆箱便于我们在要求传入对象的地方直接传入基本数据类型,或者反过来。如:

  1. 自动装箱的例子

    1
    2
    List<Integer> list = new ArrayList<>();
    list.add(1);

    因为 list 是一个 Integer 对象的集合,理论上 add 方法传进去的都应该是 Integer 对象,但是这里可以传递一个 int(数值2),就是因为编译器把 int 转换为 Integer 了。实际上编译器会把上面的代码转变为:

    1
    2
    List<Integer> list = new ArrayList<>();
    list.add(Integer.valueOf(1));
  2. 拆箱的例子

    1
    2
    3
    4
    public int getFirstNumber(List<Integer> list) {
    int firstNumber = list.get(0);
    return firstNumber;
    }

    与自动装箱相反的,在这段代码中,编译器自动把 Integer 对象转换为 int 基本数据类型。

如果没有自动装箱和拆箱,上面的代码将编译不通过。上面的代码得这样写才行:

1
2
List<Integer> list = new ArrayList<>();
list.add(Integer.valueOf(1));

1
2
3
4
public int getFirstNumber(List<Integer> list) {
int firstNumber = list.get(0).intValue();
return firstNumber;
}

可见 Java 编译器帮我们做的事情还是带来一些便利的。

什么时候会自动装箱和拆箱

自动装箱和拆箱会在以下这些情况发生:

  • 把基本数据类型赋值给一个声明为其包装类类型的变量时,像 Integer obj = 1 将发生装箱;反之拆箱。
  • 把基本数据类型作为函数调用的参数,而该参数的类型是其包装类时,像前面的 list.add(1); 将发生装箱;反之拆箱。
  • 对包装类进行加减乘除等基本运算时,将发生拆箱。像 Integer a = 0; a += 1; 在执行 += 运算的 + 运算时将发生拆箱。

可能带来的问题

虽然自动装箱与拆箱偷偷帮我们做了很多事,但是在一些情况下它也可能导致问题。

  1. 对象比较的结果
    考虑以下代码:

    1
    2
    3
    4
    5
    6
    7
    public class TestJava {
    public static void main(String[] args) {
    Integer number1 = 128;
    Integer number2 = 128;
    System.out.println("number1 == number2: " + (number1 == number2));
    }
    }

    输出结果是:number1 == number2: false
    注意这里是用 == 比较两个 Integer 对象的内存指针,不是比较他们代表的值。number1number2 是两个不同的对象,比较结果自然是 false。

    继续看以下代码:

    1
    2
    3
    4
    5
    6
    7
    public class TestJava {
    public static void main(String[] args) {
    Integer number3 = 127;
    Integer number4 = 127;
    System.out.println("number3 == number4: " + (number3 == number4));
    }
    }

    输出结果是:number3 == number4: true
    (一脸惊恐.jpg) 几乎一样的代码,出来的结果却不一样。其实是因为 Java 虚拟机对 -128到127 的 Integer 对象做了缓存,所以 number3number4 实际上是同一个对象,比较结果自然也就返回 true。

  2. 对象的过多创建
    考虑以下代码:

    1
    2
    3
    4
    Integer sum = 0;
    for (int i = 0; i < 1000; i++) {
    sum = sum + i;
    }

    上面的代码中,由于 sum 是一个 Integer 对象,不能直接进行 + 操作,所以会先执行 sum.intValue() 拆箱,得到 int 类型进行 + 操作。然后再执行 Integer.valueOf(sum) 进行装箱操作,得到一个 Integer 对象赋值给 sum。虽然 Java 虚拟机对 -128 到 127 的 Integer 对象做了缓存,但是从第 128 到 1000 次循环中总共还要创建 873 个对象。这将导致程序的性能降低甚至触发垃圾回收。而简单地把 sum 的类型改为 int 就可解决问题。

所以在混合使用 Java 基本数据类型及其包装类时要特别注意这些问题。