自动装/拆箱和valueOf陷阱

单刀直入,我们直接来看一道题:

Integer i1 = new Integer(1);//方式一
Integer i2 = 1;//方式二
Integer i3 = Integer.valueOf(1);//方式三
System.out.println(i1 == i2);//false
System.out.println(i1 == i3);//false
System.out.println(i2 == i3);//true

请问:为什么三种创建方式下的引用在比较时,会出现 falsefalsetrue 这种结果呢?
要解决分析并理解这道题,我们首先从三种创建方式来说,并简要说明一下自动装箱和自动拆箱的概念。

  • 在JDK5.0以前,基本数据类型转换成包装类都需要Integer构造器new一个对象实现,即方式一;
  • 从JDK5.0开始,引入自动装箱和自动拆箱的概念,即在代码书写上可以直接“赋值”,更加简洁方便,实际上,在编译过程中,编译器将代码自动翻译成调用valueOf()方法,此时的valueOf方法方法体内还是直接new一个对象返回;
  • 从JDK7.0开始,valueOf方法改变,引入了缓存的机制,可以将部分值提前缓存起来,有需要下直接从缓存中调取,节省空间和运行时间。
    我们来看下部分源码,以下源码均基于JDK14.0
//Integer.valueOf()源码:from JDK 9.0
public static Integer valueOf(int i) {
        if (i >= IntegerCache.low && i <= IntegerCache.high)
            return IntegerCache.cache[i + (-IntegerCache.low)];
        return new Integer(i);
    }
//IntegerCache源码
private static class IntegerCache {
        static final int low = -128;
        static final int high;
        static final Integer[] cache;
        static Integer[] archivedCache;

        static {
            // high value may be configured by property
            int h = 127;
            String integerCacheHighPropValue =
                VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
            if (integerCacheHighPropValue != null) {
                try {
                    h = Math.max(parseInt(integerCacheHighPropValue), 127);
                    // Maximum array size is Integer.MAX_VALUE
                    h = Math.min(h, Integer.MAX_VALUE - (-low) -1);
                } catch( NumberFormatException nfe) {
                    // If the property cannot be parsed into an int, ignore it.
                }
            }
            high = h;

            // Load IntegerCache.archivedCache from archive, if possible
            VM.initializeFromArchive(IntegerCache.class);
            int size = (high - low) + 1;

            // Use the archived cache if it exists and is large enough
            if (archivedCache == null || size > archivedCache.length) {
                Integer[] c = new Integer[size];
                int j = low;
                for(int i = 0; i < c.length; i++) {
                    c[i] = new Integer(j++);
                }
                archivedCache = c;
            }
            cache = archivedCache;
            // range [-128, 127] must be interned (JLS7 5.1.7)
            assert IntegerCache.high >= 127;
        }

        private IntegerCache() {}
    }

从上面源码我们可以发现,在调用Integer.valueOf方法时,会调用类IntegerCache的high和low属性与输入参数比较(low=-128,high可以设置,默认=127),如果输入参数超过这个范围,就重新new创建一个对象;如果在范围内,则直接返回已经缓存好存储好的cache数组中相应的值。由于,该数组是已经已经缓存好的,所以空间地址的确定的。

那么,我们来分析一下,为什么会出现问题一的输出结果呢?
上述结果说明什么问题?说明i1的地址和i2,i3不一样,但i2和i3是一样的。为什么会是这样的?

  • i1的地址和i2,i3不一样好理解,因为都是新建对象,开辟的内存位置不同,地址不同ok的。
  • 那么,为什么i2和i3地址一样呢?首先,第一点需要明确的,其实方式2和方式3在编译的时候,编译器会将方式2自动转成方式3进行编译,相当于为了方便,把valueOf方法省去了而已,在编译器看来,方法2和方法3是一致的,都是调用了valueOf函数。
  • 但是,我们可能会想,即便调用的是同一个方法,按理还是开辟不同的空间,地址还是不一样才对?其实不然,这从上述的Integer.valueOf()方法的定义我们就知道是怎么一回事了。因为引入了缓存的情况,我们创建的“1”是在缓存范围内的,所以我们会直接从缓存中调取结果返回,而不用new一个新对象,缓存地址是确定的,所以就会造成i2和i3相等的结果。
那么,我们知道了这个陷阱,我们再看一个问题就很明朗了!
Integer i1 = new Integer(128);
Integer i2 = 128;
Integer i3 = Integer.valueOf(128);
System.out.println(i1 == i2);
System.out.println(i1 == i3);
System.out.println(i2 == i3);

那么,输出结果就很自然可以得出:false,false,false。因为已经超出默认的范围了,所以会开辟创建新对象,而不是从缓存中获取,地址也就不相同了。

**注意:**同样的,和Integer包装类一样,整形和字符型包装类(Character、Byte、Short、Long)都存在提前缓存的情况。但是,除了这两个包装类之外,其余的包装类的创建都是直接开辟新空间,而不是从缓存中获取的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值