zoukankan      html  css  js  c++  java
  • Java 基础 装箱, 拆箱

    总结

    1-装箱过程是通过调用包装器的valueOf方法实现的,而拆箱过程是通过调用包装器的 xxxValue方法实现的。(xxx代表对应的基本数据类型)。例如:在装箱的时候自动调用的是Integer的valueOf(int)方法。而在拆箱的时候自动调用的是Integer的intValue方法。

    public class Main {
    	public static void main(String[] args) {
    		
    		Integer i = 10;
    		int n = i;
    	}
    }

     一、为什么需要包装类

    虽然 Java 语言是典型的面向对象编程语言,但其中的八种基本数据类型并不支持面向对象编程,基本类型的数据不具备“对象”的特性——不携带属性、没有方法可调用。 沿用它们只是为了迎合人类根深蒂固的习惯,并的确能简单、有效地进行常规数据处理。这种借助于非面向对象技术的做法有时也会带来不便。比如:

    1. 编码过程中只接收对象的情况,比如List中只能存入对象,不能存入基本数据类型;比如一个方法的参数是Object时,不能传入基本数据类型,但可以传入对应的包装类; 比如泛型等等。
    2. 基本数据类型没有toString()方法等

    Java中对每种基本类型都有一个对应的包装类:

    • byte -> Byte
    • short -> Short
    • int -> Integer
    • long -> Long
    • float -> Float
    • double -> Double
    • boolean -> Boolean
    • char -> Character
      其中需要注意int对应的是Integer,char对应的Character,其他6个都是基本类型首字母大写即可。

    每个包装类的对象可以封装一个相应的基本类型的数据,并提供了其它一些有用的方法。包装类对象一经创建,TODO: 其内容(所封装的基本类型数据值)不可改变(类似于String类,可看源码)。详见: Java 基础 - 如何理解String不可变

     TODO: COMPARING https://zhuanlan.zhihu.com/p/65538963

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

      在前面的文章中提到,Java为每种基本数据类型都提供了对应的包装器类型,至于为什么会为每种基本数据类型提供包装器类型在此不进行阐述,有兴趣的朋友可以查阅相关资料。在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

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

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

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

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

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

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

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

      输出结果是:

    true
    true
    true
    true

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

    public static Boolean valueOf(boolean b) {
            return (b ? TRUE : FALSE);
        }
    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);
    复制代码
     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.下面程序的输出结果是什么?

    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
    复制代码
    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方法)。

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

    ----------------------------

    参考:https://www.cnblogs.com/dolphin0520/p/3780005.html

    参考:https://zhuanlan.zhihu.com/p/65538963

    作者:Matrix海子   
    本博客中未标明转载的文章归作者Matrix海子和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
  • 相关阅读:
    p1229
    2017.01.21
    P1136 超车 归并排序 求逆序对个数
    2017年寒假计划
    递归一题总结(OJ P1117倒牛奶)
    原来scanf读入字符串还能这样..
    2016NOIP总结
    公式推♂倒题
    kmp+DP x 子串相关的计数问题
    XXXXXXXX不会太多吧?
  • 原文地址:https://www.cnblogs.com/frankcui/p/10876808.html
Copyright © 2011-2022 走看看