详解Java的自动装箱与拆箱(Autoboxing and unboxing)

 

一、什么是自动装箱拆箱 
很简单,下面两句代码就可以看到装箱和拆箱过程

1 //自动装箱
2 Integer total = 99;
3 
4 //自定拆箱
5 int totalprim = total;

 

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

下面我们来看看需要装箱拆箱的类型有哪些:

这里写图片描述

这里写图片描述

这个过程是自动执行的,那么我们需要看看它的执行过程:

按 Ctrl+C 复制代码

按 Ctrl+C 复制代码

 

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

 1 javap -c StringTest 

这里写图片描述

Integer total = 99; 
执行上面那句代码的时候,系统为我们执行了: 
Integer total = Integer.valueOf(99);

int totalprim = total; 
执行上面那句代码的时候,系统为我们执行了: 
int totalprim = total.intValue();

我们现在就以Integer为例,来分析一下它的源码: 
1、首先来看看Integer.valueOf函数

1 public static Integer valueOf(int i) {
2 return  i >= 128 || i < -128 ? new Integer(i) : SMALL_VALUES[i + 128];
3 }

它会首先判断i的大小:如果i小于-128或者大于等于128,就创建一个Integer对象,否则执行SMALL_VALUES[i + 128]。

首先我们来看看Integer的构造函数:

复制代码

1 private final int value;
2 
3 public Integer(int value) {
4     this.value = value;
5 }
6 
7 public Integer(String string) throws NumberFormatException {
8     this(parseInt(string));
9 }

复制代码

它里面定义了一个value变量,创建一个Integer对象,就会给这个变量初始化。第二个传入的是一个String变量,它会先把它转换成一个int值,然后进行初始化。

下面看看SMALL_VALUES[i + 128]是什么东西:

 1 private static final Integer[] SMALL_VALUES = new Integer[256]; 

它是一个静态的Integer数组对象,也就是说最终valueOf返回的都是一个Integer对象。

所以我们这里可以总结一点:装箱的过程会创建对应的对象,这个会消耗内存,所以装箱的过程会增加内存的消耗,影响性能。

2、接着看看intValue函数

1 @Override
2 public int intValue() {
3     return value;
4 }

这个很简单,直接返回value值即可。

二、相关问题 
上面我们看到在Integer的构造函数中,它分两种情况: 

1、i >= 128 || i < -128 =====> new Integer(i) 
2、i < 128 && i >= -128 =====> SMALL_VALUES[i + 128]

1 private static final Integer[] SMALL_VALUES = new Integer[256];

SMALL_VALUES本来已经被创建好,也就是说在i >= 128 || i < -128是会创建不同的对象,在i < 128 && i >= -128会根据i的值返回已经创建好的指定的对象。

说这些可能还不是很明白,下面我们来举个例子吧:

复制代码

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

复制代码

代码的后面,我们可以看到它们的执行结果是不一样的,为什么,在看看我们上面的说明。 
1、i1和i2会进行自动装箱,执行了valueOf函数,它们的值在(-128,128]这个范围内,它们会拿到SMALL_VALUES数组里面的同一个对象SMALL_VALUES[228],它们引用到了同一个Integer对象,所以它们肯定是相等的。

2、i3和i4也会进行自动装箱,执行了valueOf函数,它们的值大于128,所以会执行new Integer(200),也就是说它们会分别创建两个不同的对象,所以它们肯定不等。

下面我们来看看另外一个例子:

复制代码

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

复制代码

看看上面的执行结果,跟Integer不一样,这样也不必奇怪,因为它们的valueOf实现不一样,结果肯定不一样,那为什么它们不统一一下呢? 
这个很好理解,因为对于Integer,在(-128,128]之间只有固定的256个值,所以为了避免多次创建对象,我们事先就创建好一个大小为256的Integer数组SMALL_VALUES,所以如果值在这个范围内,就可以直接返回我们事先创建好的对象就可以了。

但是对于Double类型来说,我们就不能这样做,因为它在这个范围内个数是无限的。 
总结一句就是:在某个范围内的整型数值的个数是有限的,而浮点数却不是。

所以在Double里面的做法很直接,就是直接创建一个对象,所以每次创建的对象都不一样。

1 public static Double valueOf(double d) {
2     return new Double(d);
3 }

下面我们进行一个归类: 
Integer派别:Integer、Short、Byte、Character、Long这几个类的valueOf方法的实现是类似的。 
Double派别:Double、Float的valueOf方法的实现是类似的。每次都返回不同的对象。

下面对Integer派别进行一个总结,如下图: 
这里写图片描述

下面我们来看看另外一种情况:

复制代码

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

复制代码

可以看到返回的都是true,也就是它们执行valueOf返回的都是相同的对象。

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

可以看到它并没有创建对象,因为在内部已经提前创建好两个对象,因为它只有两种情况,这样也是为了避免重复创建太多的对象。

1 public static final Boolean TRUE = new Boolean(true);
2 
3 public static final Boolean FALSE = new Boolean(false);

 

上面把几种情况都介绍到了,下面来进一步讨论其他情况。

1 Integer num1 = 400;  
2 int num2 = 400;  
3 System.out.println(num1 == num2); //true
说明num1 == num2进行了拆箱操作
1 Integer num1 = 100;  
2 int num2 = 100;  
3 System.out.println(num1.equals(num2));  //true

我们先来看看equals源码:

1 @Override
2 public boolean equals(Object o) {
3     return (o instanceof Integer) && (((Integer) o).value == value);
4 }

我们指定equal比较的是内容本身,并且我们也可以看到equal的参数是一个Object对象,我们传入的是一个int类型,所以首先会进行装箱,然后比较,之所以返回true,是由于它比较的是对象里面的value值。

1 Integer num1 = 100;  
2 int num2 = 100;  
3 Long num3 = 200l;  
4 System.out.println(num1 + num2);  //200
5 System.out.println(num3 == (num1 + num2));  //true
6 System.out.println(num3.equals(num1 + num2));  //false

1、当一个基础数据类型与封装类进行==、+、-、*、/运算时,会将封装类进行拆箱,对基础数据类型进行运算。 
2、对于num3.equals(num1 + num2)为false的原因很简单,我们还是根据代码实现来说明:

1 @Override
2 public boolean equals(Object o) {
3     return (o instanceof Long) && (((Long) o).value == value);
4 }

它必须满足两个条件才为true: 
1、类型相同 
2、内容相同 
上面返回false的原因就是类型不同。

1 Integer num1 = 100;
2 Ingeger num2 = 200;
3 Long num3 = 300l;
4 System.out.println(num3 == (num1 + num2)); //true

我们来反编译一些这个class文件:javap -c StringTest 
这里写图片描述

可以看到运算的时候首先对num3进行拆箱(执行num3的longValue得到基础类型为long的值300),然后对num1和mum2进行拆箱(分别执行了num1和num2的intValue得到基础类型为int的值100和200),然后进行相关的基础运算。

我们来对基础类型进行一个测试:

1 int num1 = 100;
2 int num2 = 200;
3 long mum3 = 300;
4 System.out.println(num3 == (num1 + num2)); //true

就说明了为什么最上面会返回true.

所以,当 “==”运算符的两个操作数都是 包装器类型的引用,则是比较指向的是否是同一个对象,而如果其中有一个操作数是表达式(即包含算术运算)则比较的是数值(即会触发自动拆箱的过程)。

陷阱1:

1  Integer integer100=null;  
2  int int100=integer100;

这两行代码是完全合法的,完全能够通过编译的,但是在运行时,就会抛出空指针异常。其中,integer100为Integer类型的对象,它当然可以指向null。但在第二行时,就会对integer100进行拆箱,也就是对一个null对象执行intValue()方法,当然会抛出空指针异常。所以,有拆箱操作时一定要特别注意封装类对象是否为null。

总结: 
1、需要知道什么时候会引发装箱和拆箱 
2、装箱操作会创建对象,频繁的装箱操作会消耗许多内存,影响性能,所以可以避免装箱的时候应该尽量避免。

3、equals(Object o) 因为原equals方法中的参数类型是封装类型,所传入的参数类型(a)是原始数据类型,所以会自动对其装箱,反之,会对其进行拆箱

4、当两种不同类型用==比较时,包装器类的需要拆箱, 当同种类型用==比较时,会自动拆箱或者装箱

================================================================================================

=============================================================================================

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

  以下是本文的目录大纲:

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

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

  三.面试中相关的问题

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

  请尊重作者劳动成果,转载请标明原文链接:

   http://www.cnblogs.com/dolphin0520/p/3780005.html

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

  在前面的文章中提到,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

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

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

  我们就以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方法的具体实现:

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

  而其中IntegerCache类的实现为:

复制代码

 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);

    }

}

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

 View Code

  至于具体为什么,读者可以去查看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方法的具体实现:

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

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

复制代码

 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方法)。

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值