Java的自动拆装箱

目录(本文基于JDK1.8)


 

  • 1. Java中的基本数据类型和包装类型是什么?
  • 2. 自动拆箱和装箱的定义是什么?
  • 3. 为什么需要自动拆箱和装箱?
  • 4.自动拆箱和装箱是如何实现的?
  • 4. 什么时候会发生到自动拆箱和装箱?
  • 5.自动拆装箱需要注意的地方
    • 5.1 自动拆装箱的小陷阱
    • 5.2 Integer.valueOf()方法
    • 5.3 Java8种基本类型的自动装箱代码实现

 

1. Java中的基本数据类型和包装类型是什么?

基本数据类型 包装类型
int(4字节) Integer
short(2字节) Short
byte(1字节) Byte
long(8字节) Long
float(4字节) Float
double(8字节) Double
char(2字节) Character
boolean(未定) Boolean

2. 自动拆箱和装箱的定义是什么?

  • 自动装箱就是Java自动将原始类型值转换成对应的对象,比如将int的变量转换成Integer对象,这个过程叫做装箱,反之将Integer对象转换成int类型值,这个过程叫做拆箱。因为这里的装箱和拆箱是自动进行的非人为转换,所以就称作为自动装箱和拆箱
    //装箱
    Integer i = 10;  
    //拆箱
    int n = i;   

3. 为什么需要自动拆箱和装箱?

  • JDK1.5以下,是不能直接地向集合(Collections)中放入原始类型值,因为集合只接收对象,我们需要将这些原始类型的值转换成对象,然后将这些转换的对象放入集合中。在JDK1.5开始引入了自动拆箱装箱,目的是将原始类型值转自动地转换成对应的对象
    //在Java 5之前,只能这样做
    Integer value = new Integer(1);
    //或者这样做
    Integer value = Integer.valueOf(1);
    //直接赋值是错误的
    //Integer value = 1;

4.自动拆箱和装箱是如何实现的?

下面我们看一个小例子,并把它反编译一下查看结果。(以前我们提到过java中的编译和反编译)

    /**
     * @author Y 
     */
    public class TestPackaging {
        public static void main(String[] args) {
            Integer i = 10;
            int n = i;
        }
    }

    /*
     * 通过 CFR 0_132反编译.
     */
    package com.demo;
    
    public class TestPackaging {
        public static void main(String[] arrstring) {
            Integer n = Integer.valueOf((int)10);
            int n2 = n.intValue();
        }
    }

    /*
     * 通过javap反编译.
     */
    Compiled from "TestPackaging.java"
    public class com.demo.TestPackaging {
      public com.demo.TestPackaging();
        Code:
           0: aload_0
           1: invokespecial #1                  // Method java/lang/Object."":()V
           4: return
    
      public static void main(java.lang.String[]);
        Code:
           0: bipush        10
           2: invokestatic  #2                  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
           5: astore_1
           6: aload_1
           7: invokevirtual #3                  // Method java/lang/Integer.intValue:()I
          10: istore_2
          11: return
    }

我们可以看到,不管是通过javap还是cfr反编译出来的结果,都可以清晰的看到,在装箱的的时候调用的是Integer.valueOf()方法;在拆箱的时候调用的是IntegerintValue()方法。当然其他的类型也是类似的。

4. 什么时候会发生到自动拆箱和装箱?

  • 赋值时
    • 在Java 1.5以前我们需要手动地进行转换才行,而现在所有的转换都是由编译器来完成。
    //before autoboxing
    Integer a = Integer.valueOf(3);
    Int b = a.intValue()
     
    //after java5
    Integer a = 3; 
    int b = a; 

  • 方法调用时
    • 当我们在方法调用时,我们可以传入原始数据值或者对象,同样编译器会帮我们进行转换。
    /**
     * @author Y
     */
    public class TestPackaging {
        public static void main(String[] args) {
            int result = test(10);
        }
    
        public static Integer test(Integer i){
            System.out.println("Integer i: " + i);
            return i;
        }
    }

test()方法接收Integer对象作为参数,当调用test(10)时,会将int值转换成对应的Integer对象,这就是所谓的自动装箱,test()方法返回Integer对象,而在int result = test(10);中的 resultint类型,所以这时候发生自动拆箱操作,将test()方法的返回的 Integer对象转换成int值。

上面是我们自己用到自动拆装箱的例子,还有一种情况是我们工作中可能不注意,然后就发生了自动拆装装箱,因为当赋值符号或者运算符号两边的类型一个为基本数据类型一个为其包装类时,就会发生自动装箱或者自动拆箱。例如三目运算,这里请看一下H大的例子,你真的了解Java中的三目运算符吗?

5.自动拆装箱需要注意的地方

5.1 自动拆装箱的小陷阱

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

    /**
     * @author Y
     */
    public class TestPackaging {
        public static void main(String[] args) {
            int a1 = 10;
            int a2 = 10;
    
            Integer b1 = 10;
            Integer b2 = 10;
    
            Integer b3 = new Integer(10);
            Integer b4 = new Integer(10);
    
            Integer b5 = 200;
            Integer b6 = 200;
    
            Integer b7 = 0;
            Integer b8 = new Integer(0);
    
            Long c = 20L;
            Double d1 = 1.0;
            Double d2 = 1.0;
    
            System.out.println("(a1=a2)\t" + (a1 == a2));
            System.out.println("(b1=b2)\t" + (b1 == b2));
            System.out.println("(a1=b1)\t" + (a1 == b1));
            System.out.println("(b3=b4)\t" + (b3 == b4));
            System.out.println("(a1=b3)\t" + (a1 == b3));
            System.out.println("(b1=b3)\t" + (b1 == b3));
            System.out.println("(b5=b6)\t" + (b5 == b6));
            System.out.println("(b1=b2+b7)\t" + (b1 == b2 + b7));
            System.out.println("(b3=b4+b8)\t" + (b3 == b4 + b8));
            System.out.println("(c=b1+b2)\t" + (c == b1 + b2));
            System.out.println("(c.equals(b1+b2))\t" + (c.equals(b1 + b2)));
            System.out.println("(d1=d2)\t" + (d1 == d2));
        }

请看下面的输出结果跟你预期的一样吗?

(a1=a2) true

(b1=b2) true

(a1=b1) true

(b3=b4) false

(a1=b3) true

(b1=b3) false

(b5=b6) false

(b1=b2+b7) true

(b3=b4+b8) true

(c=b1+b2) true

(c.equals(b1+b2)) false

(d1=d2) false

自动装箱时编译器调用valueOf将原始类型值转换成对象,同时自动拆箱时,编译器通过调用类似intValue(),doubleValue()这类的方法将对象转换成原始类型值。

5.2 Integer.valueOf()方法

    /**
     * 这个方法总是在范围-128到127之间缓存值
     */
    public static Integer valueOf(int i) {
        //如果i的值在 - 128 到 127(可配置)之间,取IntegerCache.cache中缓存的对象返回,否则new一个新实例
        if (i >= IntegerCache.low && i <= IntegerCache.high)
            return IntegerCache.cache[i + (-IntegerCache.low)];
        return new Integer(i);
    }
    
    //静态内部类 存放 -128 - 127(默认)之间的缓存Integer对象
    //运行时可通过-XX:AutoBoxCacheMax=1000指定缓存最大值:
    private static class IntegerCache {
        //最小值
        static final int low = -128;
        //最大值,可配置
        static final int high;
        //缓存
        static final Integer cache[];

        static {
            //最大值可以由属性来控制,默认最大127
            int h = 127;
            String integerCacheHighPropValue =
                sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
            if (integerCacheHighPropValue != null) {
                try {
                    //参数解析成int
                    int i = parseInt(integerCacheHighPropValue);
                    i = Math.max(i, 127);
                    // Maximum array size is Integer.MAX_VALUE
                    //最大值范围 Integer.MAX_VALUE - 128 - 1 ;
                    h = Math.min(i, Integer.MAX_VALUE - (-low) -1);
                } catch( NumberFormatException nfe) {
                    // If the property cannot be parsed into an int, ignore it.
                }
            }
            high = h;
            //创建cache,并对cache进行初始化
            cache = new Integer[(high - low) + 1];
            int j = low;
            for(int k = 0; k < cache.length; k++)
                cache[k] = new Integer(j++);

            // range [-128, 127] must be interned (JLS7 5.1.7)
            assert IntegerCache.high >= 127;
        }
        //私有构造函数,不能类外实例化,这个内部类不需要实例,只需要静态的存储数组cache
        private IntegerCache() {}
    }

自动装箱时,首先判断值是否在-128127之间,如果在-128127之间则直接从IntegerCache.cache缓存中获取指定数字的包装类;不存在则new出一个新的包装类。IntegerCache.cache内部实现了一个Integer的静态常量数组,在类加载的时候,执行static静态块进行初始化-128127之间的Integer对象,存放到cache数组中。cache属于常量,存放在Java的方法区中。所以上面的代码中,只要是自动装箱,并且值都在-128127之间的都是true,当然new Integer()那种是两个不同的对象,所以是false。还有当 ==运算符的两个操作数都是 包装器类型的引用,则是比较指向的是否是同一个对象,而如果其中有一个操作数是表达式(即包含算术运算)则比较的是数值(即会触发自动拆箱的过程)。另外,对于包装器类型,equals方法并不会进行类型转换。

5.3 Java8种基本类型的自动装箱代码实现

    //boolean原生类型自动装箱成Boolean
    public static Boolean valueOf(boolean b) {
        return (b ? TRUE : FALSE);
    }

    //byte原生类型自动装箱成Byte
    public static Byte valueOf(byte b) {
        final int offset = 128;
        return ByteCache.cache[(int)b + offset];
    }

    //byte原生类型自动装箱成Byte
    public static Short valueOf(short s) {
        final int offset = 128;
        int sAsInt = s;
        if (sAsInt >= -128 && sAsInt <= 127) { // must cache
            return ShortCache.cache[sAsInt + offset];
        }
        return new Short(s);
    }

    //char原生类型自动装箱成Character
    public static Character valueOf(char c) {
        if (c <= 127) { // must cache
            return CharacterCache.cache[(int)c];
        }
        return new Character(c);
    }
    
    //int原生类型自动装箱成Integer
    public static Integer valueOf(int i) {
        if (i >= IntegerCache.low && i <= IntegerCache.high)
            return IntegerCache.cache[i + (-IntegerCache.low)];
        return new Integer(i);
    }

    //int原生类型自动装箱成Long
    public static Long valueOf(long l) {
        final int offset = 128;
        if (l >= -128 && l <= 127) { // will cache
            return LongCache.cache[(int)l + offset];
        }
        return new Long(l);
    }

    //double原生类型自动装箱成Double
    public static Double valueOf(double d) {
        return new Double(d);
    }

    //float原生类型自动装箱成Float
    public static Float valueOf(float f) {
        return new Float(f);
    }

通过上面的代码我们可以看到,FloatDouble的自动装箱代码没有使用缓存,每次都是new 新的对象,其它的6种基本类型都使用了缓存

最后我们先看一下这段代码反编译后的结果


  public static void main(String[] paramArrayOfString) {
    int i = 10;
    int j = 10;
    
    Integer localInteger1 = Integer.valueOf(10);
    Integer localInteger2 = Integer.valueOf(10);
    
    Integer localInteger3 = new Integer(10);
    Integer localInteger4 = new Integer(10);
    
    Integer localInteger5 = Integer.valueOf(200);
    Integer localInteger6 = Integer.valueOf(200);
    
    Integer localInteger7 = Integer.valueOf(0);
    Integer localInteger8 = new Integer(0);
    
    Long localLong = Long.valueOf(20L);
    Double localDouble1 = Double.valueOf(1.0D);
    Double localDouble2 = Double.valueOf(1.0D);
    
    //值比较所以为true
    System.out.println("(a1=a2)\t" + (i == j)); //true
    //只要在-128和127之间的自动装箱对象都从缓存中获取的,所以b1和b2的引用指向同一个对象,所以为true
    System.out.println("(b1=b2)\t" + (localInteger1 == localInteger2));//true
    //拆箱后进行了值比较所以为true
    System.out.println("(a1=b1)\t" + (i == localInteger1.intValue()));//true
    //两个不同的对象所以为false
    System.out.println("(b3=b4)\t" + (localInteger3 == localInteger4));//false
    //拆箱后做了值比较
    System.out.println("(a1=b3)\t" + (i == localInteger3.intValue()));//true
    //装箱后在缓存取的对象,和一个新对象比较随意为false
    System.out.println("(b1=b3)\t" + (localInteger1 == localInteger3));//false
    //没有在-128和127之间,所以是新对象,所以为false
    System.out.println("(b5=b6)\t" + (localInteger5 == localInteger6));//false
    //拆箱后比较的值,所以为true
    System.out.println("(b1=b2+b7)\t" + (localInteger1.intValue() == localInteger2.intValue() + localInteger7.intValue()));//true
    //拆箱后比较的值,所以为true
    System.out.println("(b3=b4+b8)\t" + (localInteger3.intValue() == localInteger4.intValue() + localInteger8.intValue()));//true
    //拆箱后比较的值,所以为true
    System.out.println("(c=b1+b2)\t" + (localLong.longValue() == localInteger1.intValue() + localInteger2.intValue()));//true
    //equals方法并不会进行类型转换,所以为false
    System.out.println("(c.equals(b1+b2))\t" + localLong.equals(Integer.valueOf(localInteger1.intValue() + localInteger2.intValue())));//false
    //Double没有缓存,所以是新对象,所以为false
    System.out.println("(d1=d2)\t" + (localDouble1 == localDouble2));//false
  }

 

 

 

参考资料:

链接:Autoboxing and Unboxing (The Java Tutorials > Lea...

链接:深入剖析Java中的装箱和拆箱 - 海 子 - 博客园

链接:Java 自动装箱与拆箱的实现原理 - 简书

你可能感兴趣的:(Java自动拆装箱)