深入剖析Java中的装箱和拆箱,深入剖析java装箱

自动装箱(boxing)和自动拆箱(unboxing)

首先了解下Java的四类八种基本数据类型

基本类型 占用空间(Byte) 表示范围 包装器类型

boolean

1/8

true|false

Boolean

char

2

-128~127

Character

byte

1

-128~127

Byte

short

2

-2ˆ15~2ˆ15-1

Short

int

4

-2ˆ31~2ˆ31-1

Integer

long

8

-2ˆ63~2ˆ63-1

Long

float

4

-3.403E38~3.403E38

Float

double

8

-1.798E308~1.798E308

Double

深入剖析Java中的装箱和拆箱,深入剖析java装箱

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

自动装箱

Java中所谓的装箱通俗点就是:八种基本数据类型在某些条件下使用时,会自动变为对应的包装器类型。

如下清单1:

@Test
public void boxingTest() {

Integer i1 = 17;
Integer i2 = 17;

Integer i3 = 137;
Integer i4 = 137;

System.out.println(i1 == i2);
11 System.out.println(i3 == i4);
}

输出:

true
false

解释下清单1第11句输出true的原因:

当包装器类型进行“==”比较时,i3会调用Integer.valueOf自动装箱基本数据类型为包装器类型。

/**
* Returns an {@code Integer} instance representing the specified
* {@code int} value. If a new {@code Integer} instance is not
* required, this method should generally be used in preference to
* the constructor {@link #Integer(int)}, as this method is likely
* to yield significantly better space and time performance by
* caching frequently requested values.
*
* This method will always cache values in the range -128 to 127,
* inclusive, and may cache other values outside of this range.
*
* @param i an {@code int} value.
* @return an {@code Integer} instance representing {@code i}.
* @since 1.5
*/
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}

从源码中可以看出,Integer对象自动缓存int值范围在low~high(-128~127),如果超出这个范围则会自动装箱为包装类。

Note:

  1. Integer、Short、Byte、Character、Long这几个包装类的valueOf方法的实现是类似的;
  2. Double、Float的valueOf方法的实现是类似的。
  3. Boolean的valueOf方法的实现是个三目运算,形如`  return (b ? TRUE : FALSE);  `

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

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

 

1 Integer i = new Integer(10);

而在从Java
SE5开始就提供了自动装箱的特性,如果要生成一个数值为10的Integer对象,只需要这样就可以了:

 

1 Integer i = 10;

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

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

 

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

自动拆箱

Java中所谓的拆箱通俗点就是:八种包装器类型在某些条件下使用时,会自动变为对应的基本数据类型。

清单2:

@Test
public void unboxingTest() {
Integer i1 = 17;
int i2 = 17;

int i3 = 137;
Integer i4 = 137;

System.out.println(i1 == i2);
10 System.out.println(i3 == i4);

}

输出:

true
true

解释下清单2第10句输出true的原因:

当程序执行到第10句时,i4会调用Integer.intValue方法自动拆箱包装器类型为基本数据类型。

/**
* Returns the value of this {@code Integer} as an
* {@code int}.
*/
public int intValue() {
return value;
}

从源码可以看出,当包装器类型和基本数据类型进行“==”比较时,包装器类型会自动拆箱为基本数据类型。

清单3内容如下:

@Test
public void unboxingTest() {
Integer i1 = 17;
Integer i2 = 17;

Integer i3 = 137;
Integer i4 = 137;

// ==
System.out.println(i1 == i2);
System.out.println(i3 == i4);

// equals
System.out.println(i1.equals(i2));
15 System.out.println(i3.equals(i4));

}

输出:

true
false
true
true

解释第15句为什么会输出true:

因为在Integer包装类实现的equals方法中,只要比较的当前对象是Integer实例,那么就会自动拆箱为基本数据类型。从以下Integer类的equals方法的源码就可看出:

/**
* Compares this object to the specified object. The result is
* {@code true} if and only if the argument is not
* {@code null} and is an {@code Integer} object that
* contains the same {@code int} value as this object.
*
* @param obj the object to compare with.
* @return {@code true} if the objects are the same;
* {@code false} otherwise.
*/
public boolean equals(Object obj) {
if (obj instanceof Integer) {
return value == ((Integer)obj).intValue();
}
return false;
}

Note:

  1. Integer、Short、Byte、Character、Long这几个包装类的intValue方法的实现是类似的;
  2. Double、Float的intValue方法的实现是类似的。
  3. Boolean的booleanValue方法的实现和intValue方法的实现也是类似的。

装箱拆箱综合清单:

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;

// 会自动拆箱(会调用intValue方法)
System.out.println(c==d);
// 会自动拆箱后再自动装箱
System.out.println(e==f);
// 虽然“==”比较的是引用的是否是同一对象,但这里有算术运算,如果该引用为包装器类型则会导致自动拆箱
System.out.println(c==(a+b));
// equals 比较的是引用的对象的内容(值)是否相等,但这里有算术运算,如果该引用为包装器类型则会导
 // 致自动拆箱,再自动装箱
// a+b触发自动拆箱得到值后,再自动装箱与c比较
System.out.println(c.equals(a+b));
// 首先a+b触发自动拆箱后值为int型,所以比较的是值是否相等
System.out.println(g==(a+b));
// 首先a+b触发自动拆箱后值为int型,自动装箱后为Integer型,然后g为Long型
System.out.println(g.equals(a+b));
// 首先a+h触发自动拆箱后值为long型,因为int型的a会自动转型为long型的g然后自动装箱后为Long型,
 // 而g也为Long型
System.out.println(g.equals(a+h));

}

输出:

true
false
true
true
true
false
true

 这里面需要注意的是:
“==”运算符的两个操作数都是包装器类型的引用,则是比较指向的是否是同一个对象,而如果其中有一个操作数是表达式(即包含算术运算)则比较的是数值(即会触发自动拆箱的过程)
另外,对于包装器类型,equals方法并不会进行类型转换。

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

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

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

 

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

反编译class文件之后得到如下内容:

图片 1

从反编译得到的字节码内容可以看出,在装箱的时候自动调用的是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的实现。

图片 2

在这里只解释一下为什么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方法)。

如果对上面的具体执行过程有疑问,可以尝试获取反编译的字节码内容进行查看。

文章参考:深入剖析Java中的装箱和拆箱

自动装箱和拆箱问题是Java中一个老生常谈的问题了,今天我们就来一些看一下装箱和拆箱中…

发表评论

电子邮件地址不会被公开。 必填项已用*标注