Java中的自动装箱与拆箱

自动装箱和拆箱从Java 1.5开始引入,目的是将原始类型值转自动地转换成对应的对象。自动装箱与拆箱的机制可让咱们在Java的变量赋值或者是方法调用等状况下使用原始类型或者对象类型更加简单直接。 java

若是你在Java1.5下进行过编程的话,你必定不会陌生这一点,你不能直接地向集合(Collections)中放入原始类型值,由于集合只接收对象。一般这种状况下你的作法是,将这些原始类型的值转换成对象,而后将这些转换的对象放入集合中。使用Integer,Double,Boolean等这些类咱们能够将原始类型值转换成对应的对象,可是从某些程度可能使得代码不是那么简洁精炼。为了让代码简练,Java 1.5引入了具备在原始类型和对象类型自动转换的装箱和拆箱机制。可是自动装箱和拆箱并不是完美,在使用时须要有一些注意事项,若是没有搞明白自动装箱和拆箱,可能会引发难以察觉的bug。 android

本文将介绍,什么是自动装箱和拆箱,自动装箱和拆箱发生在何时,以及要注意的事项。 编程

什么是自动装箱和拆箱

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

自动装箱拆箱要点

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

什么时候发生自动装箱和拆箱

自动装箱和拆箱在Java中很常见,好比咱们有一个方法,接受一个对象类型的参数,若是咱们传递一个原始类型值,那么Java会自动讲这个原始类型值转换成与之对应的对象。最经典的一个场景就是当咱们向ArrayList这样的容器中增长原始类型数据时或者是建立一个参数化的类,好比下面的ThreadLocal。 app

1
2
3
4
5
6
7
8
9
ArrayList<Integer> intList =newArrayList<Integer>();
intList.add(1);//autoboxing - primitive to object
intList.add(2);//autoboxing
 
ThreadLocal<Integer> intLocal =newThreadLocal<Integer>();
intLocal.set(4);//autoboxing
 
intnumber = intList.get(0);// unboxing
intlocal = intLocal.get();// unboxing in Java

举例说明

上面的部分咱们介绍了自动装箱和拆箱以及它们什么时候发生,咱们知道了自动装箱主要发生在两种状况,一种是赋值时,另外一种是在方法调用的时候。为了更好地理解这两种状况,咱们举例进行说明。 性能

赋值时

这是最多见的一种状况,在Java 1.5之前咱们须要手动地进行转换才行,而如今全部的转换都是由编译器来完成。 优化

1
2
3
4
5
6
7
//before autoboxing
Integer iObject = Integer.valueOf(3);
Int iPrimitive = iObject.intValue()
 
//after java5
Integer iObject =3;//autobxing - primitive to wrapper conversion
intiPrimitive = iObject;//unboxing - object to primitive conversion

方法调用时

这是另外一个经常使用的状况,当咱们在方法调用时,咱们能够传入原始数据值或者对象,一样编译器会帮咱们进行转换。 google

1
2
3
4
5
6
7
8
publicstaticInteger show(Integer iParam){
   System.out.println("autoboxing example - method invocation i: "+ iParam);
   returniParam;
}
 
//autoboxing and unboxing in method invocation
show(3);//autoboxing
intresult = show(3);//unboxing because return type of method is Integer

show方法接受Integer对象做为参数,当调用show(3)时,会将int值转换成对应的Integer对象,这就是所谓的自动装箱,show方法返回Integer对象,而int result = show(3);中result为int类型,因此这时候发生自动拆箱操做,将show方法的返回的Integer对象转换成int值。 spa

自动装箱的弊端

自动装箱有一个问题,那就是在一个循环中进行自动装箱操做的状况,以下面的例子就会建立多余的对象,影响程序的性能。 对象

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

上面的代码sum+=i能够当作sum = sum + i,可是+这个操做符不适用于Integer对象,首先sum进行自动拆箱操做,进行数值相加操做,最后发生自动装箱操做转换成Integer对象。其内部变化以下

1
2
sum = sum.intValue() + i;
Integer sum =newInteger(result);

因为咱们这里声明的sum为Integer类型,在上面的循环中会建立将近4000个无用的Integer对象,在这样庞大的循环中,会下降程序的性能而且加剧了垃圾回收的工做量。所以在咱们编程时,须要注意到这一点,正确地声明变量类型,避免由于自动装箱引发的性能问题。

重载与自动装箱

当重载赶上自动装箱时,状况会比较有些复杂,可能会让人产生有些困惑。在1.5以前,value(int)和value(Integer)是彻底不相同的方法,开发者不会由于传入是int仍是Integer调用哪一个方法困惑,可是因为自动装箱和拆箱的引入,处理重载方法时稍微有点复杂。一个典型的例子就是ArrayList的remove方法,它有remove(index)和remove(Object)两种重载,咱们可能会有一点小小的困惑,其实这种困惑是能够验证并解开的,经过下面的例子咱们能够看到,当出现这种状况时,不会发生自动装箱操做。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
publicvoidtest(intnum){
    System.out.println("method with primitive argument");
 
}
 
publicvoidtest(Integer num){
    System.out.println("method with wrapper argument");
 
}
 
//calling overloaded method
AutoboxingTest autoTest =newAutoboxingTest();
intvalue =3;
autoTest.test(value);//no autoboxing
Integer iValue = value;
autoTest.test(iValue);//no autoboxing
 
Output:
method with primitive argument
method with wrapper argument

要注意的事项

自动装箱和拆箱可使代码变得简洁,可是其也存在一些问题和极端状况下的问题,如下几点须要咱们增强注意。

对象相等比较

这是一个比较容易出错的地方,”==“能够用于原始值进行比较,也能够用于对象进行比较,当用于对象与对象之间比较时,比较的不是对象表明的值,而是检查两个对象是不是同一对象,这个比较过程当中没有自动装箱发生。进行对象值比较不该该使用”==“,而应该使用对象对应的equals方法。看一个能说明问题的例子。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
publicclassAutoboxingTest {
 
    publicstaticvoidmain(String args[]) {
 
        // Example 1: == comparison pure primitive – no autoboxing
        inti1 =1;
        inti2 =1;
        System.out.println("i1==i2 : "+ (i1 == i2));// true
 
        // Example 2: equality operator mixing object and primitive
        Integer num1 =1;// autoboxing
        intnum2 =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 =newInteger(1);// no autoboxing
        Integer anotherOne =newInteger(1);
        System.out.println("one == anotherOne : "+ (one == anotherOne));// false
 
    }
 
}
 
Output:
i1==i2 :true
num1 == num2 :true
obj1 == obj2 :true
one == anotherOne :false

值得注意的是第三个小例子,这是一种极端状况。obj1和obj2的初始化都发生了自动装箱操做。可是处于节省内存的考虑,JVM会缓存-128到127的Integer对象。由于obj1和obj2其实是同一个对象。因此使用”==“比较返回true。

容易混乱的对象和原始数据值

另外一个须要避免的问题就是混乱使用对象和原始数据值,一个具体的例子就是当咱们在一个原始数据值与一个对象进行比较时,若是这个对象没有进行初始化或者为Null,在自动拆箱过程当中obj.xxxValue,会抛出NullPointerException,以下面的代码

1
2
3
4
5
6
privatestaticInteger count;
 
//NullPointerException on unboxing
if( count <=0){
  System.out.println("Count is not started yet");
}

缓存的对象

这个问题就是咱们上面提到的极端状况,在Java中,会对-128到127的Integer对象进行缓存,当建立新的Integer对象时,若是符合这个这个范围,而且已有存在的相同值的对象,则返回这个对象,不然建立新的Integer对象。

在Java中另外一个节省内存的例子就是字符串常量池,感兴趣的同窗能够了解一下。

生成无用对象增长GC压力

由于自动装箱会隐式地建立对象,像前面提到的那样,若是在一个循环体中,会建立无用的中间对象,这样会增长GC压力,拉低程序的性能。因此在写循环时必定要注意代码,避免引入没必要要的自动装箱操做。

如想了解垃圾回收和内存优化,能够查看本文Google IO:Android内存管理主题演讲记录

相关文章
相关标签/搜索