桃园结义 , 版权所有丨如未注明 , 均为原创丨转载请注明

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

桃园小编 370次浏览 0个评论 扫描二维码

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

    在JAVA SE5之前,如果要生成一个数值为10的Integer对象,必须这样:

Integer i = new Integer(10);

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

Integer i = 10;

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

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

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

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

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

以Integer类为例,下面看一段代码:

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.下面这段代码的输出结果是什么?

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);  //true
        System.out.println(i3==i4); //false
    }
}

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

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

    其中 IntegerCache类的实现为:

private static class IntegerCache {
        static final int low = -128;
        static final int high;
        static final Integer cache[];

        static {
            // high value may be configured by property
            int h = 127;
            String integerCacheHighPropValue =
                sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
            if (integerCacheHighPropValue != null) {
                int i = parseInt(integerCacheHighPropValue);
                i = Math.max(i, 127);
                // Maximum array size is Integer.MAX_VALUE
                h = Math.min(i, Integer.MAX_VALUE - (-low) -1);
            }
            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() {}
    }

    看源码,在通过valueOf方法创建Integer对象的时候,如果数值在[-128,127]之间,便返回指向IntegerCache.cache中已经存在的对象的引用。否则创建一个新的Integer对象返回。

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

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

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); //false
        System.out.println(i3==i4); //false
    }
}

    至于具体为什么,可以插口Double类的valueOf的实现。

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

⚠️注意:Integer、Short、Byte、Character、Long这个类的valueOf方法的实现是类似的。

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

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

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); //true
        System.out.println(i3==i4); //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);

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

    这题目属于比较宽泛类型的,但要点一定要答上:

    1:第一种方式不会触发自动装箱的过程,第二种方式会触发。

    2:在执行效率和资源占用上的区别,第二种方式的执行效率和资源占用在一般性情况下要优于第一种情况。(不是绝对的)。

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

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));
    }
}
true
false
true
true
true
false
true

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

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


四、总结

 Java使用自动装箱和拆箱机制,节省了常用数值的内存开销和创建对象的开销,提高了效率。

    两个Integer对象直接也可以用>、<等符号比较大小,两个Integer对象都拆箱后,在比较大小。

    所以装箱后的对象,最好不要用 “==”比较。因为 -128~127范围(一般是这个范围)内取缓存对象用,所以相等,该范围外是两个不同对象引用比较,所以不等。

其他数据类型对应的包装类型的自动装箱池大小:

    Byte,Short,Long对对应的是-128~127.

    Character对应的是 0~127.

    Float 和Double没有自动装箱池.

原文优化转载:http://www.cnblogs.com/dolphin0520/p/3780005.html

百度已收录

桃园结义 , 版权所有丨如未注明 , 均为原创丨转载请注明深入剖析Java中的装箱和拆箱

您必须 登录 才能发表评论!