Integer自动装箱细节分析

来源:互联网 发布:圣诞节淘宝网页设计 编辑:程序博客网 时间:2024/06/11 02:11

装箱 就是将基本的数据类型(比如int)封装成对象类型(比如Integer)。拆箱 就是将对象类型拆包装为基本类型。

装箱和拆箱操作为我们提供了很多便利。

  • 包装类,如Integer给我们提供的友好的API方法,使我们可以快速完成数据类型之间的转换。
  • Java是面向对象的,我们在Java中处理的绝大多数都是对象,在类似Map集合类等很多场合中,我们需要放入Integer这种对象类型。

很多情况下,编译器为我们自动完成了基本数据类型的拆箱和装箱操作。

public class TestInteger{    public static void main(String[] args){        int a1 = 127;    //auto-boxing        Integer i = a1;    //auto-unboxing        int b1 = i;    }}

下面,我们来看一段代码:

public class TestInteger{         public static void main(String[] args){            int a1 = 12;            //auto-boxing            Integer number1 = a1;            Integer number2 = a1;            System.out.println("number1==number2:"+ (number1==number2));            //out:number1==number2:true            int b1=128;            Integer number3 = b1;            Integer number4 = b1;            System.out.println("number3==number4:"+ (number3==number4));            //out:number3==number4:false        }}

通过编译,我们发现Integer类型在auto-boxing操作时,-128到127之间的int类型整数封装之后的对象是同一个,这个范围之外的int类型整数封装之后对象是不同的。通过后面的分析,我们可以知道:

-128到127这个范围,-128是固定的,上限127是我们可以通过Jvm参数配置的。

为什么会这样呢?我们通过这段程序的字节码分析这段程序编译的细节:

Microsoft Windows [版本 10.0.15063](c) 2017 Microsoft Corporation。保留所有权利。D:\JavaProject\SuperMarket\bin\com\javaexc\service>javap -c Test.classCompiled from "Test.java"public class com.javaexc\service.Test {  public com.javaexc\service.Test();    Code:       0: aload_0       1: invokespecial #8                  // Method java/lang/Object."<init>":()V       4: return  public static void main(java.lang.String[]);    Code:       0: bipush        12       2: istore_1       3: iload_1       4: invokestatic  #16                 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;       7: astore_2       8: iload_1       9: invokestatic  #16                 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;      12: astore_3      13: getstatic     #22                 // Field java/lang/System.out:Ljava/io/PrintStream;      16: new           #28                 // class java/lang/StringBuilder      19: dup      20: ldc           #30                 // String number1==number2:      22: invokespecial #32                 // Method java/lang/StringBuilder."<init>":(Ljava/lang/String;)V      25: aload_2      26: aload_3      27: if_acmpne     34      30: iconst_1      31: goto          35      34: iconst_0      35: invokevirtual #35                 // Method java/lang/StringBuilder.append:(Z)Ljava/lang/StringBuilder;      38: invokevirtual #39                 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;      41: invokevirtual #43                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V      44: sipush        128      47: istore        4      49: iload         4      51: invokestatic  #16                 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;      54: astore        5      56: iload         4      58: invokestatic  #16                 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;      61: astore        6      63: getstatic     #22                 // Field java/lang/System.out:Ljava/io/PrintStream;      66: new           #28                 // class java/lang/StringBuilder      69: dup      70: ldc           #48                 // String number3==number4:      72: invokespecial #32                 // Method java/lang/StringBuilder."<init>":(Ljava/lang/String;)V      75: aload         5      77: aload         6      79: if_acmpne     86      82: iconst_1      83: goto          87      86: iconst_0      87: invokevirtual #35                 // Method java/lang/StringBuilder.append:(Z)Ljava/lang/StringBuilder;      90: invokevirtual #39                 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;      93: invokevirtual #43                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V      96: return}

通过观察4、9、51、58这四行字节码我们发现,编译器调用Integer.valueOf()帮助我们完成自动装箱操作。

我们进一步阅读Integer.class文件,分析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);    }

当输入参数i满足i >= IntegerCache.low && i <= IntegerCache.high这个范围时,return的是IntegerCache.cache[]这个数组里的元素,否则return一个新的Integer对象。
那么IntegerCache.lowIntegerCache.high以及IntegerCache.cache[]分别是什么呢?我们继续看代码:

 /**     * Cache to support the object identity semantics of autoboxing for values between     * -128 and 127 (inclusive) as required by JLS.     *     * The cache is initialized on first usage.  The size of the cache     * may be controlled by the -XX:AutoBoxCacheMax=<size> option.     * During VM initialization, java.lang.Integer.IntegerCache.high property     * may be set and saved in the private system properties in the     * sun.misc.VM class.     */    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));            }            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() {}    }

我们看到IntegerCache.lowIntegerCache.high以及IntegerCache.cache[]都是static final修饰的,IntegerCache.low固定是-128,IntegerCache.high可以通过设置属性来修改,默认是127。

结论1:
int类型整数自动装箱时,当值在上述范围内时,返回的是IntegerCache.cache[]这个数组里的同一个元素。

这也就是-128到127之间的int类型整数自动封装成同一个对象的原因。这里的IntegerCache就是用来处理Integer自动装箱操作的。

在自动装箱时,当值在[128,127]之间时,引用指向的都是内存中的同一个对象。如果超过这个范围,每次装箱都会new()一个新的对象。这样便加大了对简单数字的重复使用,在某些场景下可以优化我们代码的性能。

结论2:
值得说明的是,上述情况只有在我们使用自动装箱时,编译器自动调用Integer.valueOf()才会出现,如果我们采用new()来创建对象,每一次new()的对象都是一个新的对象。
原创粉丝点击