zoukankan      html  css  js  c++  java
  • 深入解析Java中的装箱和拆箱

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

      下面是本文的文件夹大纲:

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

      二.装箱和拆箱是怎样实现的

      三.面试中相关的问题

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

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

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

    为什么会出现这种结果?输出结果表明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.以下这段代码的输出结果是什么?

    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方法不同的实现。非常easy:在某个范围内的整型数值的个数是有限的,而浮点数却不是。

      注意,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
    
    

    至于为什么是这个结果,相同地。看了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.以下程序的输出结果是什么?

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

  • 相关阅读:
    LeetCode 40. 组合总和 II(Combination Sum II)
    LeetCode 129. 求根到叶子节点数字之和(Sum Root to Leaf Numbers)
    LeetCode 60. 第k个排列(Permutation Sequence)
    LeetCode 47. 全排列 II(Permutations II)
    LeetCode 46. 全排列(Permutations)
    LeetCode 93. 复原IP地址(Restore IP Addresses)
    LeetCode 98. 验证二叉搜索树(Validate Binary Search Tree)
    LeetCode 59. 螺旋矩阵 II(Spiral Matrix II)
    一重指针和二重指针
    指针的意义
  • 原文地址:https://www.cnblogs.com/mengfanrong/p/5419993.html
Copyright © 2011-2022 走看看