Java的自动拆装箱

2019/06/18 Java

装箱和拆箱的概念描述的其实就是Java中八种基本数据类型和对应的包装类型之间的转换过程。我们把基本数据类型转换成对应的包装类型的过程叫做装箱。反之就是拆箱。在Java中的装箱和拆箱不是人为操作的,是程序在编译的时候编译器帮助我们完成这项任务的,因此说它是自动的。


自动拆装箱概念

  • 自动装箱就是Java自动将原始类型值转换成对应的对象,比如将int的变量转换成Integer对象,这个过程叫做装箱.
  • 反之将Integer对象转换成int类型值,这个过程叫做拆箱。
  • 这里的装箱和拆箱是自动进行的非人为转换,所以就称作为自动装箱和拆箱。
  • 原始类型byte,short,int,long,float,double,boolean,char。
  • 对应的封装类为Byte,Short,Integer,Long,Float,Double,Boolean,Character。

自动拆装箱原理

  • 自动装箱时编译器调用valueOf将原始类型值转换成对象。
  • 同时自动拆箱时,编译器通过调用类似intValue(),doubleValue()这类的方法将对象转换成原始类型值。
  • 自动装箱是将byte值转换成Byte对象,short转换成Short,int转换成Integer, long转换成Long,float值转换成Float对象,double值转化为Double对象,boolean值转换成Boolean对象,char转换成Character对象。自动拆箱则是相反的操作。

自动拆装箱发生场景

  • 自动装箱和拆箱在Java中很常见,比如我们有一个方法,接受一个对象类型的参数,如果我们传递一个原始类型值,那么Java会自动讲这个原始类型值转换成与之对应的对象。
  • 最经典的一个场景就是当我们向ArrayList这样的容器中增加原始类型数据时或者是创建一个参数化的类,比如下面的ThreadLocal。
  • 在赋值时和方法调用时,经常发生自动拆装箱。
ArrayList<Integer> intList = new ArrayList<Integer>();
intList.add(1); //autoboxing - primitive to object
intList.add(2); //autoboxing

ThreadLocal<Integer> intLocal = new ThreadLocal<Integer>();
intLocal.set(4); //autoboxing

int number = intList.get(0); // unboxing
int local = intLocal.get(); // unboxing in Java

自动装箱的弊端

  • 在变量进行运算的时候,对象不支持运算符运算,会把对象拆箱为变量。操作完之后,会再次装箱为变量,创建大量无用的对象。
Integer sum = 0;
for(int i=1000; i<5000; i++){
   sum+=i; //首先,sum对象先拆箱,进行运算之后,再次装箱为Integer。
}

重载和自动装箱

  • 当重载遇上自动装箱时,情况会比较有些复杂,可能会让人产生有些困惑。在1.5之前,value(int)和value(Integer)是完全不相同的方法,开发者不会因为传入是int还是Integer调用哪个方法困惑,但是由于自动装箱和拆箱的引入,处理重载方法时稍微有点复杂。一个典型的例子就是ArrayList的remove方法,它有remove(index)和remove(Object)两种重载,我们可能会有一点小小的困惑,其实这种困惑是可以验证并解开的,通过下面的例子我们可以看到,当出现这种情况时,不会发生自动装箱操作。
public class AutoboxingTest{
    public void test(int num){
        System.out.println("method with primitive argument");
    }

    public void test(Integer num){
        System.out.println("method with wrapper argument");
    }
}

//calling overloaded method
AutoboxingTest autoTest = new AutoboxingTest();
int value = 3;
autoTest.test(value); //no autoboxing 
Integer iValue = value;
autoTest.test(iValue); //no autoboxing

Output:
method with primitive argument
method with wrapper argument

注意事项

  • 对象相等值比较
    • ”==”可以用于原始值进行比较,也可以用于对象进行比较.
    • 当用于对象与对象之间比较时,比较的不是对象代表的值,而是检查两个对象是否是同一对象,这个比较过程中没有自动装箱发生。进行对象值比较不应该使用”==“,而应该使用对象对应的equals方法。看一个能说明问题的例子。
public class AutoboxingTest {

    public static void main(String args[]) {

        // Example 1: == comparison pure primitive – no autoboxing
        int i1 = 1;
        int i2 = 1;
        System.out.println("i1==i2 : " + (i1 == i2)); // true

        // Example 2: equality operator mixing object and primitive
        Integer num1 = 1; // autoboxing
        int num2 = 1;
        System.out.println("num1 == num2 : " + (num1 == num2)); // true

        // Example 3: special case - arises due to autoboxing in Java
        Integer obj1 = 1; // autoboxing will call Integer.valueOf()
        Integer obj2 = 1; // same call to Integer.valueOf() will return same
                            // cached Object

        System.out.println("obj1 == obj2 : " + (obj1 == obj2)); // true

        // Example 4: equality operator - pure object comparison
        Integer one = new Integer(1); // no autoboxing
        Integer anotherOne = new Integer(1);
        System.out.println("one == anotherOne : " + (one == anotherOne)); // false

    }

}

Output:
i1==i2 : true
num1 == num2 : true
obj1 == obj2 : true
one == anotherOne : false

  • 容易混乱的对象和原始数据值
    • 一个原始数据值与一个对象进行比较时,如果这个对象没有进行初始化或者为Null,在自动拆箱过程中obj.xxxValue,会抛出NullPointerException,如下面的代码
  • 生成无用对象增加GC压力
    • 因为自动装箱会隐式地创建对象,像前面提到的那样,如果在一个循环体中,会创建无用的中间对象,这样会增加GC压力,拉低程序的性能。所以在写循环时一定要注意代码,避免引入不必要的自动装箱操作。

Integer缓存

  • 在Java5中,为Integer的操作引入了一个新的特性,用来节省内存和提高性能。整型对象在内部实现中通过使用相同的对象引用实现了缓存和重用,上面的规则适用于整数区间 -128 到 127。
  • Integer缓存策略仅在自动装箱(autoboxing)的时候有用,使用构造器创建的 Integer 对象不能被缓存。
// 没有自动装箱
Integer integer1 = new Integer(3);
Integer integer2 = 3;   
System.out.println(integer1 == integer2);

output: false
  • Integer对象的hash值为数值本身。

Search

    Table of Contents