Java中基本类型和包装类的各类比较(==),以及包装类的对象缓存池
Java中的基本类型及其包装类的比较(==)一直是一个比较头疼的问题,不仅有自动装箱和拆箱操作,部分的包装类还有对象缓存池,这就导致了这部分知识容易混淆。
对于==操作符来说,如果比较的数据是基本类型,则比较它们的值,如果比较的是对象,则会比较对象的内存地址。另外,如果一个是基本类型、一个是包装类型,在比较前会先把包装类型拆箱成基本类型,然后进行比较。
以int为例,这里我们把参与比较的类型分为三种:int、直接new出来的Integer对象和自动装箱出来的Integer对象。
这里先不考虑Integer的缓存池,我们对三种类型之间的两两比较进行下排列组合,3 * 3一共有9种可能。由于==运算符是不区分左右的先后顺序的,也就是说a == b跟b == a等价,所以可以去除3种重复比较,这样就只有6种情况了。具体如下图:
接下来我们依次看下这六种情况,下面上代码:
情况①:
// ①int 与 int 类型比较
int i1 = 128;
int i2 = 128;
System.out.println("int == int, result:" + (i1 == i2));// true
System.out.println("----------------------------------------------------");
基本类型使用==比较的是值,所以这个毫无疑问就是true。
情况②:
// ②int类型 和 new出来的Integer对象 比较
int i3 = 128;
Integer i4 = new Integer(128);
// 在进行运算前,i4会自动拆箱,实质是两个int类型比较
System.out.println("int == new Integer, result:" + (i3 == i4));// true
System.out.println("----------------------------------------------------");
基本类型跟包装类型比较,包装类会先自动拆箱,然后再比较,实质上还是两个int在比较,所以这里结果还是true。
情况③:
// ③int类型 和 自动装箱的Integer对象 比较
int i5 = 128;
Integer i6 = 128;
// 在进行运算前,i6会自动拆箱,实质是两个int类型比较
System.out.println("int == Integer autoBoxing, result:" + (i5 == i6));// true
System.out.println("----------------------------------------------------");
这里的i6在赋值的时候,会自动把128装箱成Integer类型,接着使用==比较的时候,i6又会自动拆箱成int,所以实质上还是两个int在比较,跟情况②原理一样。
情况④:
// ④两个new出来的Integer对象的内存地址比较
Integer i7 = new Integer(128);
Integer i8 = new Integer(128);
System.out.println("new Integer == new Integer, result:" + (i7 == i8));// false
System.out.println("----------------------------------------------------");
这里是两个new出来的包装类对象进行比较,这里比较的是他们各自对象的内存地址,由于是两个不同的对象,所以地址值必然不同,结果是false。
情况⑤:
// ⑤自动装箱的Integer类型 和 new出来的Integer对象 比较
Integer i9 = 128;
Integer i10 = new Integer(128);
System.out.println("Integer autoBoxing == new Integer, result:" + (i9 == i10));// false
System.out.println("----------------------------------------------------");
这里的i9在赋值时会将128自动装箱成Integer类型,所以最终是两个不同对象的内存地址比较,结果必然为false。
情况⑥:
// ⑥自动装箱的Integer对象 和 自动装箱的Integer对象 比较,区间[-128, 127]之外
Integer i11 = 128;
Integer i12 = 128;
System.out.println("Integer autoBoxing == Integer autoBoxing, 区间[-128, 127]之外,result:" + (i11 == i12));// false
System.out.println("----------------------------------------------------");
这里的i10和i11在赋值时都会先将128自动装箱成Integer类型,然后再进行比较,原理跟情况⑤一样,所以最终结果为false。
细心的同学可能已经发现,我这里举例用的数字是128,选择这个数字的意义在于在比较时不涉及Integer的缓存池。
Integer的缓存池
关于Integer的缓存池,我们还是举例说明:
// 自动装箱会从缓存池中取对象,缓存池的区间为[-128, 127]
// 自动装箱的Integer对象 和 自动装箱的Integer对象 比较,区间[-128, 127]中
Integer i13 = 127;// 缓存池
Integer i14 = 127;// 缓存池
System.out.println("Integer autoBoxing == Integer autoBoxing, 区间[-128, 127]中,result:" + (i13 == i14));// true
这个例子跟上面的情况⑥一模一样,唯一不同是这里使用的是127,情况⑥使用的是128,但是情况⑥的结果是false,这里的比较结果却是true,这就很让人疑惑。
我们分析下这个过程,i13和i14的赋值过程中,均会触发自动装箱,给127生成对应的Integer对象;接下来使用==比较这两个Integer对象的地址,最后的输出结果为true。
我们知道,Java中如果两个对象的内存地址一样,那么他们就是同一个对象,分配的就是同一块内存。我们这里的i13和i14都是自动装箱产生的,是不是在产生过程中返回了同一个对象?
对自动装箱和拆箱有疑问的同学可以看一文带你理解Java中自动装箱和拆箱。
Integer对应的自动装箱的方法是Integer#valueOf(),我们看下它的实现:
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
可以看到如果i的值在某个范围内的话,就会从IntegerCache.cache这个数组中取出一个Integer对象返回;如果超出了这个范围的话就直接new一个Integer对象返回。
我们看下IntegerCache的代码:
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;
...
high = h;
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;
}
private IntegerCache() {}
}
按照惯例,省略掉了无关代码。
这里的下限low的值默认为-128,上限high的值默认为127,cache是一个Integer类型的数组。
static代码块里面主要做了两件事: ①初始化cache这个Integer数组,数组容量为256。 ②给cache的所有元素赋值。在for循环里面,依次给cache的各个元素进行赋值,Integer的值从-128开始,一直到127,对应的是闭区间[-128, 127],刚好256个元素。
所以我们常说的Integer的缓存池其实就是这里的IntegerCache.cache,它在Integer进行类加载的时候初始化。
在基本类型int需要自动装箱的时候,就会调用Integer#valueOf方法,在Integer#valueOf方法的实现中,如果int的值在IntegerCache.cache缓存的范围内(闭区间[-128, 127]),就返回已经构建好的Integer对象,具体是根据角标从IntegerCache.cache这个数组中去取。
经过上面的讲解可以知道,我们这里的127经过自动装箱后,是从缓存池中拿的Integer对象,所以i13和i14拿到的是同一个Integer对象(因为它们的值相同,在IntegerCache.cache数组中对应的index相同,所以获取到的是同一个缓存对象)。
Byte、Short、Integer、Long、Character、Boolean对应的缓存池。
既然Integer有缓存池,那其他的包装类是否也有同样的情况呢?
我们看下测试代码:
// 自动装箱的Byte对象 和 自动装箱的Byte对象 比较,区间[-128, 127]中
Byte b1 = 127;// 缓存池
Byte b2 = 127;// 缓存池
System.out.println("Byte autoBoxing == Byte autoBoxing, 区间[-128, 127]中,result:" + (b1 == b2));// true
System.out.println("----------------------------------------------------");
// 自动装箱的Short对象 和 自动装箱的Short对象 比较,区间[-128, 127]中
Short s1 = 127;// 缓存池
Short s2 = 127;// 缓存池
System.out.println("Short autoBoxing == Short autoBoxing, 区间[-128, 127]中,result:" + (s1 == s2));// true
System.out.println("----------------------------------------------------");
// 自动装箱的Long对象 和 自动装箱的Long对象 比较,区间[-128, 127]中
Long l1 = 127L;// 缓存池
Long l2 = 127L;// 缓存池
System.out.println("Long autoBoxing == Long autoBoxing, 区间[-128, 127]中,result:" + (l1 == l2));// true
System.out.println("----------------------------------------------------");
// 自动装箱的Character对象 和 自动装箱的Character对象 比较,区间[-128, 127]中
Character c1 = 127;// 缓存池
Character c2 = 127;// 缓存池
System.out.println("Character autoBoxing == Character autoBoxing, 区间[-128, 127]中,result:" + (c1 == c2));// true
System.out.println("----------------------------------------------------");
// 自动装箱的Boolean对象 和 自动装箱的Boolean对象 比较,区间[-128, 127]中
Boolean bool1 = true;// 缓存池
Boolean bool2 = true;// 缓存池
System.out.println("Boolean autoBoxing == Boolean autoBoxing, 区间[true, false]中,result:" + (bool1 == bool2));// true
System.out.println("----------------------------------------------------");
基本类型对应的包装类中,Byte、Short、Integer、Long和Character的缓存池都是[-128, 127],Boolean的缓存池比较特殊,只有true和false两个Boolean对象。
总结如下: