单刀直入,我们直接来看一道题:
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
请问:为什么三种创建方式下的引用在比较时,会出现 false
、false
、true
这种结果呢?
要解决分析并理解这道题,我们首先从三种创建方式来说,并简要说明一下自动装箱和自动拆箱的概念。
- 在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)都存在提前缓存的情况。但是,除了这两个包装类之外,其余的包装类的创建都是直接开辟新空间,而不是从缓存中获取的。