美文网首页
Java自动装、拆箱解析

Java自动装、拆箱解析

作者: Goo_Yao | 来源:发表于2017-01-10 14:12 被阅读70次

    前言

    之前虽然略有听闻自动装箱、自动拆箱,却一直不清楚装箱、拆箱到底是什么,直到今天看到一段有趣的小程序,决定研究一番装箱与拆箱。

        public static void main(String[] args) {
            Integer i1 = 30;
            Integer i2 = 30;
            Integer i3 = 128;
            Integer i4 = 128;
            System.out.println(i1 == i2);
            System.out.println(i3 == i4);
        }
        //输出结果:
        //true
        //false
        //why? 抱着疑问,开始探索!
    
    • 基本概念
      • 自动装箱:八种基本数据类型在某些条件下使用时候,会自动变为对应的包装类型。(上面的代码,就是自动装箱的一种)
      • 自动拆箱:八种包装类型在某些条件下使用时候,会自动变成对应的基本数据类型。
    • 简单来说,代码表示是这样的
      • 自动装箱:Integer i = 10;(int -> Integer)
      • 自动拆箱:int n = i;(Integer -> int)

    附:八种基本数据类型

    基本类型 占用空间(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

    认识自动装箱

    回看看开头的代码,第一次输出true,很合乎情理,而第二次输出却是false,这就很让人疑惑了。

        public static void main(String[] args) {
             Integer i1 = 30;
             Integer i2 = 30;
             Integer i3 = 128;
             Integer i4 = 128;
             System.out.println(i1 == i2);
             System.out.println(i3 == i4);
         }
         //输出结果:
         //true
         //false
         //why? 抱着疑问,开始探索!
    

    解析:
    当包装器类进行“==”比较时,内部会调用 Integer.valueOf方法进行自动装箱(int -> Integer)

      /**
         * 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对象内部有IntegerCache类,可缓存(-128127范围的数值),如果超过了,则会返回一个新的Integer类。由于“==”比较的是内存地址,因此,在“-128127”数值范围内,比较的是同一个对象,得到true,而超过了该范围,则是返回自动装箱后的新对象,因此得到false。

    总结:

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

    认识自动拆箱

    再看一段代码演示自动拆箱:

        public static void main(String[] args)  {
            Integer i1 = 30;
            int i2 = 30;
            int i3 = 128;
            Integer i4 = 128;
            System.out.println(i1 == i2);
            System.out.println(i3 == i4);
        }
        //输出结果:
        //true
        //true
    

    解析:
    当与基本类型进行“==”比较时,包装器类会调用intValue方法进行自动拆箱(Integer -> int)

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

    从源码中可见,直接返回真实值,没有范围限制,因此两次输出均为true

    装箱与拆箱如何实现?

    看到这里,可能会有人疑问:“从哪里可用看出自动装箱调用了valueOf方法而自动拆箱则调用了intValue方法呢?”答案便是 - 通过反编译class文件,下面作简单演示:

    public class StudyJava{
        public static void main(String[] args){
            Integer i = 1;
            int i2 = i;
        }
    }
    

    编译后,在控制台中使用 javap -c StudyJava 命令可得到:

    Compiled from "StudyJava.java"
    public class StudyJava {
      public StudyJava();
        Code:
           0: aload_0
           1: invokespecial #1                  // Method java/lang/Object."<init>":()V
           4: return
      public static void main(java.lang.String[]);
        Code:
           0: iconst_1
           1: invokestatic  #2                  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
           4: astore_1
           5: aload_1
           6: invokevirtual #3                  // Method java/lang/Integer.intValue:()I
           9: istore_2
          10: return
    }
    

    从反编译得到的字节码中不难看出,装箱(int -> Integer)时候的确调用了Integer.valueOf方法;拆箱时则调用Integer.intValue方法

    需要注意的地方

    • Double、Float的valueOf方法的实现与Integer、Short、Byte、Character、Long这几个类的实现方法有区别。
      原因:在一定范围内,整数数值是有限的,而对于浮点数,则不是。
    /**
         * Returns a {@code Double} instance representing the specified
         * {@code double} value.
         * If a new {@code Double} instance is not required, this method
         * should generally be used in preference to the constructor
         * {@link #Double(double)}, as this method is likely to yield
         * significantly better space and time performance by caching
         * frequently requested values.
         *
         * @param  d a double value.
         * @return a {@code Double} instance representing {@code d}.
         * @since  1.5
         */
        public static Double valueOf(double d) {
            //并没有缓存,直接返回一个新的实例化对象
            return new Double(d);
        }
    

    明白源码中的实现后,相信大家对下面代码输出结果也都理解了吧~

    public class StudyJava{
        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
    //fasle
    
    • 接下来再提一下Boolean类,装箱valueOf实现代码如下
    /**
         * Returns a {@code Boolean} instance representing the specified
         * {@code boolean} value.  If the specified {@code boolean} value
         * is {@code true}, this method returns {@code Boolean.TRUE};
         * if it is {@code false}, this method returns {@code Boolean.FALSE}.
         * If a new {@code Boolean} instance is not required, this method
         * should generally be used in preference to the constructor
         * {@link #Boolean(boolean)}, as this method is likely to yield
         * significantly better space and time performance.
         *
         * @param  b a boolean value.
         * @return a {@code Boolean} instance representing {@code b}.
         * @since  1.4
         */
        public static Boolean valueOf(boolean b) {
            //TRUE、FALSE为两个内部定义的静态成员,这里直接返回两者其一
            // public static final Boolean TRUE = new Boolean(true);
            // public static final Boolean FALSE = new Boolean(false);
            return (b ? TRUE : FALSE);
        }
    

    既然返回的是内部定义静态成员,只要值相同,那么就是同一个对象,因此下面代码就很好理解啦

    public class StudyJava{
        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
    
    • 稍微复杂一点呢?
     public class StudyJava{
        public static void main(String[] args){
           Integer i1 = 1;
           Integer i2 = 2;
           Integer i3 = 200;
           int i4 = i1 + 1;
           int i5 = 200;
           Integer i6 = 200;
           Long l1 = 3L;
           Long l2 = 2L;
           System.out.println(i4 == i2); - true
           System.out.println(i2.equals(i4)); - true
           System.out.println(i3 == i5); - true
           System.out.println(i3.equals(i5)); - true
           System.out.println(i3 == i6); - false
           System.out.println(i3.equals(i6)); - true
           // System.out.println(l2==i2);//该行为错误代码,无法通过编译,类型不同
           System.out.println(l1 == (i1 + i2)); - true//可见其中进行了一些操作,使得两者可以比较
           System.out.println(l2.equals(i1 + i2)); - false
        }
    }
    //输出结果:
    //true
    //true
    //true
    //true
    //false
    //true
    //true
    //false
    

    其中值得注意的,是最后两行代码:
    “l1 == (i1 + i2)”其中包含了算术运算,会触发自动拆箱(算术运算需要自动拆箱,各自调用intValue方法得到基本类型),再进行自动装箱,最终他们进行了数值比较,因此可以正常编译;
    而“l2.equals(i1 + i2)”则是,先触发“i1 + i2”的自动拆箱(算术运算需要自动拆箱,各自调用intValue方法得到基本类型),算术运算得到数值后,再进行数值对应类型的自动装箱(valueOf),得到Integer实例对象,最后再进行equals比较。

    结语

    弄懂原理以及自动装、拆箱的时机,外加一点心细,就能较好地掌握本知识点啦,建议大家多动手实验,亲自验证一番,肯定有更多的收获的。

    相关文章

      网友评论

          本文标题:Java自动装、拆箱解析

          本文链接:https://www.haomeiwen.com/subject/clbkbttx.html