Integer的缓存和自动拆装箱

先看一个简单的例子:

public class TestInteger {

    public static void main(String[] args) {
        System.out.println("----20----");
        littleNum();
        System.out.println();
        System.out.println("----250----");
        bigNum();
    }
    
    public static void littleNum(){
        Integer i1 = 20;                //1
        Integer i2 = 20;                //2
        Integer i3 = new Integer(20);   //3
        Integer i4 = new Integer(20);   //4
        int i5 = 20;                    //5
        System.out.println("i1==i2?: " + (i1 == i2));
        System.out.println("i1==i3?: " + (i1 == i3));
        System.out.println("i1==i5?: " + (i1 == i5));
        System.out.println("i3==i4?: " + (i3 == i4));
        System.out.println("i3==i5?: " + (i3 == i5));
    }
    
    public static void bigNum(){
        Integer i1 = 250;               //1
        Integer i2 = 250;               //2
        Integer i3 = new Integer(250);  //3
        Integer i4 = new Integer(250);  //4
        int i5 = 250;                   //5
        System.out.println("i1==i2?: " + (i1 == i2));
        System.out.println("i1==i3?: " + (i1 == i3));
        System.out.println("i1==i5?: " + (i1 == i5));
        System.out.println("i3==i4?: " + (i3 == i4));
        System.out.println("i3==i5?: " + (i3 == i5));
    }
    
}

打印结果是:

----20----
i1==i2?: true
i1==i3?: false
i1==i5?: true
i3==i4?: false
i3==i5?: true

----250----
i1==i2?: false
i1==i3?: false
i1==i5?: true
i3==i4?: false
i3==i5?: true

结果好像跟想象中的不太一样,这里涉及到自动拆装箱的问题。

自动装箱和拆箱从Java 1.5开始引入,自动装箱与拆箱的机制可以让我们在Java的变量赋值或者是方法调用等情况下使用原始类型或者对象类型更加简单直接。

自动装箱就是Java自动将基本类型值转换成对应包装类的对象,比如将int的变量转换成Integer对象,这个过程叫做装箱,反之将Integer对象转换成int类型值,这个过程叫做拆箱。 这里的拆装箱是由编译器自动完成的,所以就称作为自动装箱和拆箱。

基本类型包括byte,short,char,int,long,float,double和boolean对应的封装类为Byte,Short,Character,Integer,Long,Float,Double,Boolean。

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

我们都知道对于 == 而言,在比较基本数据类型的时候是比较的它们的值,而在比较引用数据类型的时候是比较它们的内存地址。

所以,对于 i1==i3?: false 以及 i3==i4?: false 而言,后者是通过new产生的一个新对象,内存地址不同,结果为false;

而对于i1==i5?: true以及i3==i5?: true而言,i5是基本数据类型,==比较的是它们的值,所以结果都是true;

[blockquote]

说到 == 的比较,这里要补充一下,Integer类里面重写了equals()方法,所以equals()方法比较的是值

[/blockquote]

说完了 == 的比较,下面来说一下i1==i2 为什么对于20和对250的结果不一样。

这里就牵涉到Integer的缓存,在 Java 5 中,为 Integer 的操作引入了一个新的特性,用来节省内存和提高性能。整型对象在内部实现中通过使用相同的对象引用实现了缓存和重用。

在Integer的源码中,有一个IntegerCache的内部类,实现了Integer的缓存:

/**
     * 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. During VM initialization the
     * getAndRemoveCacheProperties method may be used to get and remove any system
     * properites that configure the cache size. At this time, the size of the
     * cache may be controlled by the -XX:AutoBoxCacheMax=<size> option.
     */

    // value of java.lang.Integer.IntegerCache.high property (obtained during VM init)
    private static String integerCacheHighPropValue;

    static void getAndRemoveCacheProperties() {
        if (!sun.misc.VM.isBooted()) {
            Properties props = System.getProperties();
            integerCacheHighPropValue =
                (String)props.remove("java.lang.Integer.IntegerCache.high");
            if (integerCacheHighPropValue != null)
                System.setProperties(props);  // remove from system props
        }
    }

    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;
            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() {}
    }

这里可以看到,一般而言对于 -128-127之间的Integer类型,IntegerCache内部使用了一个Integer的数组来保存它们,而在Integer的valueOf()方法中,调用了IntegerCache的这个缓存池。

valueOf()的源代码如下:

/**
     * 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.
     *
     * @param  i an {@code int} value.
     * @return an {@code Integer} instance representing {@code i}.
     * @since  1.5
     */
    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);
    }

回到之前的问题上来,在-128-127之间的Integer类型直接从缓存里面取,而在这之外的Integer类型会通过new创建一个新的对象。

这种 Integer 缓存策略仅在自动装箱的时候有用,使用构造器创建的 Integer 对象不能被缓存。因为在自动装箱过程中会调用Integer的valueOf()方法。

回头细看一下IntegerCache这个类的javadoc,Javadoc 详细的说明这个类是用来实现缓存支持,并支持 -128 到 127 之间的自动装箱过程。最大值 127 可以通过 JVM 的启动参数 -XX:AutoBoxCacheMax=size 修改。 这个缓存会在 Integer 类第一次被使用的时候被初始化出来。以后,就可以使用缓存中包含的实例对象,而不是创建一个新的实例(在自动装箱的情况下)。 实际上在 Java 5 中引入这个特性的时候,范围是固定的 -128 至 +127。后来在 Java 6 中,最大值映射到 java.lang.Integer.IntegerCache.high,可以使用 JVM 的启动参数设置最大值。

除了Integer之外,其他的一些包装类型也存在类似的缓存机制,比如Short型:

private static class ShortCache {
        private ShortCache(){}

        static final Short cache[] = new Short[-(-128) + 127 + 1];

        static {
            for(int i = 0; i < cache.length; i++)
                cache[i] = new Short((short)(i - 128));
        }
    }

和Integer不同的是,Short的范围是固定在-128-127不变的。

Byte,Short,Long 有固定范围: -128 到 127。对于 Character, 范围是 0 到 127。除了 Integer 可以通过参数改变范围外,其它的都不行。