深刻剖析Java中的装箱和拆箱

原文出处: 海子面试

自动装箱和拆箱问题是Java中一个老生常谈的问题了,今天咱们就来一些看一下装箱和拆箱中的若干问题。本文先讲述装箱和拆箱最基本的东西,再来看一下面试笔试中常常遇到的与装箱、拆箱相关的问题。编程

如下是本文的目录大纲:架构

一.什么是装箱?什么是拆箱?网站

二.装箱和拆箱是如何实现的ui

三.面试中相关的问题spa

如有不正之处,请谅解和批评指正,不胜感激。code

一.什么是装箱?什么是拆箱?

在前面的文章中提到,Java为每种基本数据类型都提供了对应的包装器类型,至于为何会为每种基本数据类型提供包装器类型在此不进行阐述,有兴趣的朋友能够查阅相关资料。在Java SE5以前,若是要生成一个数值为10的Integer对象,必须这样进行:orm

 

1
Integer i =  new  Integer( 10 );

而在从Java SE5开始就提供了自动装箱的特性,若是要生成一个数值为10的Integer对象,只须要这样就能够了:对象

 

1
Integer i =  10 ;

这个过程当中会自动根据数值建立对应的 Integer对象,这就是装箱。blog

那什么是拆箱呢?顾名思义,跟装箱对应,就是自动将包装器类型转换为基本数据类型:

 

1
2
Integer i =  10 ;   //装箱
int  n = i;    //拆箱

简单一点说,装箱就是  自动将基本数据类型转换为包装器类型;拆箱就是  自动将包装器类型转换为基本数据类型。

下表是基本数据类型对应的包装器类型:

int(4字节) Integer
byte(1字节) Byte
short(2字节) Short
long(8字节) Long
float(4字节) Float
double(8字节) Double
char(2字节) Character
boolean(未定) Boolean

二.装箱和拆箱是如何实现的

上一小节了解装箱的基本概念以后,这一小节来了解一下装箱和拆箱是如何实现的。

咱们就以Interger类为例,下面看一段代码:

 

1
2
3
4
5
6
7
public  class  Main {
     public  static  void  main(String[] args) {
 
         Integer i =  10 ;
         int  n = i;
     }
}

反编译class文件以后获得以下内容:

从反编译获得的字节码内容能够看出,在装箱的时候自动调用的是Integer的valueOf(int)方法。而在拆箱的时候自动调用的是Integer的intValue方法。

其余的也相似,好比Double、Character,不相信的朋友能够本身手动尝试一下。

所以能够用一句话总结装箱和拆箱的实现过程:

装箱过程是经过调用包装器的valueOf方法实现的,而拆箱过程是经过调用包装器的 xxxValue方法实现的。(xxx表明对应的基本数据类型)。

三.面试中相关的问题

虽然大多数人对装箱和拆箱的概念都清楚,可是在面试和笔试中遇到了与装箱和拆箱的问题却不必定会答得上来。下面列举一些常见的与装箱/拆箱有关的面试题。

1.下面这段代码的输出结果是什么?

 

1
2
3
4
5
6
7
8
9
10
11
12
public  class  Main {
     public  static  void  main(String[] args) {
 
         Integer i1 =  100 ;
         Integer i2 =  100 ;
         Integer i3 =  200 ;
         Integer i4 =  200 ;
 
         System.out.println(i1==i2);
         System.out.println(i3==i4);
     }
}

也许有些朋友会说都会输出false,或者也有朋友会说都会输出true。可是事实上输出结果是:

true
false

为何会出现这样的结果?输出结果代表i1和i2指向的是同一个对象,而i3和i4指向的是不一样的对象。此时只需一看源码便知究竟,下面这段代码是Integer的valueOf方法的具体实现:

1
2
3
4
5
6
public  static  Integer valueOf( int  i) {
         if (i >= - 128  && i <= IntegerCache.high)
             return  IntegerCache.cache[i +  128 ];
         else
             return  new  Integer(i);
     }

而其中IntegerCache类的实现为:

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
private  static  class  IntegerCache {
         static  final  int  high;
         static  final  Integer cache[];
 
         static  {
             final  int  low = - 128 ;
 
             // high value may be configured by property
             int  h =  127 ;
             if  (integerCacheHighPropValue !=  null ) {
                 // Use Long.decode here to avoid invoking methods that
                 // require Integer's autoboxing cache to be initialized
                 int  i = Long.decode(integerCacheHighPropValue).intValue();
                 i = Math.max(i,  127 );
                 // Maximum array size is Integer.MAX_VALUE
                 h = Math.min(i, Integer.MAX_VALUE - -low);
             }
             high = h;
 
             cache =  new  Integer[(high - low) +  1 ];
             int  j = low;
             for ( int  k =  0 ; k < cache.length; k++)
                 cache[k] =  new  Integer(j++);
         }
 
         private  IntegerCache() {}
     }

从这2段代码能够看出,在经过valueOf方法建立Integer对象的时候,若是数值在[-128,127]之间,便返回指向IntegerCache.cache中已经存在的对象的引用;不然建立一个新的Integer对象。

上面的代码中i1和i2的数值为100,所以会直接从cache中取已经存在的对象,因此i1和i2指向的是同一个对象,而i3和i4则是分别指向不一样的对象。

2.下面这段代码的输出结果是什么?

 

1
2
3
4
5
6
7
8
9
10
11
12
public  class  Main {
     public  static  void  main(String[] args) {
 
         Double i1 =  100.0 ;
         Double i2 =  100.0 ;
         Double i3 =  200.0 ;
         Double i4 =  200.0 ;
 
         System.out.println(i1==i2);
         System.out.println(i3==i4);
     }
}

也许有的朋友会认为跟上面一道题目的输出结果相同,可是事实上却不是。实际输出结果为:

false
false

至于具体为何,读者能够去查看Double类的valueOf的实现。

在这里只解释一下为何Double类的valueOf方法会采用与Integer类的valueOf方法不一样的实现。很简单:在某个范围内的整型数值的个数是有限的,而浮点数却不是。

注意,Integer、Short、Byte、Character、Long这几个类的valueOf方法的实现是相似的。

Double、Float的valueOf方法的实现是相似的。

3.下面这段代码输出结果是什么:

 

1
2
3
4
5
6
7
8
9
10
11
12
public  class  Main {
     public  static  void  main(String[] args) {
 
         Boolean i1 =  false ;
         Boolean i2 =  false ;
         Boolean i3 =  true ;
         Boolean i4 =  true ;
 
         System.out.println(i1==i2);
         System.out.println(i3==i4);
     }
}

输出结果是:

true
true

至于为何是这个结果,一样地,看了Boolean类的源码也会一目了然。下面是Boolean的valueOf方法的具体实现:

1
2
3
public  static  Boolean valueOf( boolean  b) {
         return  (b ? TRUE : FALSE);
     }

而其中的 TRUE 和FALSE又是什么呢?在Boolean中定义了2个静态成员属性:

1
2
3
4
5
6
7
public  static  final  Boolean TRUE =  new  Boolean( true );
 
     /**
      * The <code>Boolean</code> object corresponding to the primitive 
      * value <code>false</code>.
      */
     public  static  final  Boolean FALSE =  new  Boolean( false );

至此,你们应该明白了为什么上面输出的结果都是true了。

4.谈谈Integer i = new Integer(xxx)和Integer i =xxx;这两种方式的区别。

固然,这个题目属于比较宽泛类型的。可是要点必定要答上,我总结一下主要有如下这两点区别:

1)第一种方式不会触发自动装箱的过程;而第二种方式会触发;

2)在执行效率和资源占用上的区别。第二种方式的执行效率和资源占用在通常性状况下要优于第一种状况(注意这并非绝对的)。

5.下面程序的输出结果是什么?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public  class  Main {
     public  static  void  main(String[] args) {
 
         Integer a =  1 ;
         Integer b =  2 ;
         Integer c =  3 ;
         Integer d =  3 ;
         Integer e =  321 ;
         Integer f =  321 ;
         Long g = 3L;
         Long h = 2L;
 
         System.out.println(c==d);
         System.out.println(e==f);
         System.out.println(c==(a+b));
         System.out.println(c.equals(a+b));
         System.out.println(g==(a+b));
         System.out.println(g.equals(a+b));
         System.out.println(g.equals(a+h));
     }
}

先别看输出结果,读者本身想一下这段代码的输出结果是什么。这里面须要注意的是:当 “==”运算符的两个操做数都是 包装器类型的引用,则是比较指向的是不是同一个对象,而若是其中有一个操做数是表达式(即包含算术运算)则比较的是数值(即会触发自动拆箱的过程)。另外,对于包装器类型,equals方法并不会进行类型转换。明白了这2点以后,上面的输出结果便一目了然:

true
false
true
true
true
false
true

第一个和第二个输出结果没有什么疑问。第三句因为  a+b包含了算术运算,所以会触发自动拆箱过程(会调用intValue方法),所以它们比较的是数值是否相等。而对于c.equals(a+b)会先触发自动拆箱过程,再触发自动装箱过程,也就是说a+b,会先各自调用intValue方法,获得了加法运算后的数值以后,便调用Integer.valueOf方法,再进行equals比较。同理对于后面的也是这样,不过要注意倒数第二个和最后一个输出的结果(若是数值是int类型的,装箱过程调用的是Integer.valueOf;若是是long类型的,装箱调用的Long.valueOf方法)。

若是对上面的具体执行过程有疑问,能够尝试获取反编译的字节码内容进行查看。

若是有哪位朋友有补充的内容,欢迎下方留言,不胜感激。

问啊-定制化IT教育平台,牛人一对一服务,有问必答,开发编程社交头条 官方网站:www.wenaaa.com

QQ群290551701 汇集不少互联网精英,技术总监,架构师,项目经理!开源技术研究,欢迎业内人士,大牛及新手有志于从事IT行业人员进入!

相关文章
相关标签/搜索