Java中基本类型和包装类的各类比较(==),以及包装类的对象缓存池

4,282 阅读7分钟

Java中的基本类型及其包装类的比较(==)一直是一个比较头疼的问题,不仅有自动装箱和拆箱操作,部分的包装类还有对象缓存池,这就导致了这部分知识容易混淆。

对于==操作符来说,如果比较的数据是基本类型,则比较它们的,如果比较的是对象,则会比较对象的内存地址。另外,如果一个是基本类型、一个是包装类型,在比较前会先把包装类型拆箱成基本类型,然后进行比较。

以int为例,这里我们把参与比较的类型分为三种:int、直接new出来的Integer对象自动装箱出来的Integer对象

这里先不考虑Integer的缓存池,我们对三种类型之间的两两比较进行下排列组合,3 * 3一共有9种可能。由于==运算符是不区分左右的先后顺序的,也就是说a == bb == a等价,所以可以去除3种重复比较,这样就只有6种情况了。具体如下图:

类型/类型intnew IntegerInteger AutoBoxing
int
new Integer
Integer AutoBoxing

接下来我们依次看下这六种情况,下面上代码:

情况①:

// ①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,这就很让人疑惑。

我们分析下这个过程,i13i14的赋值过程中,均会触发自动装箱,给127生成对应的Integer对象;接下来使用==比较这两个Integer对象的地址,最后的输出结果为true

我们知道,Java中如果两个对象的内存地址一样,那么他们就是同一个对象,分配的就是同一块内存。我们这里的i13i14都是自动装箱产生的,是不是在产生过程中返回了同一个对象?

对自动装箱和拆箱有疑问的同学可以看一文带你理解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的值默认为127cache是一个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对象,所以i13i14拿到的是同一个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("----------------------------------------------------");

基本类型对应的包装类中,ByteShortIntegerLongCharacter的缓存池都是[-128, 127]Boolean的缓存池比较特殊,只有true和false两个Boolean对象。

总结如下:

基本类型包装类缓存池
byteByte[-128, 127]
shortShort[-128, 127]
intInteger[-128, 127]
longLong[-128, 127]
charCharacter[-128, 127]
booleanBoolean[true, false]
floatFloat
doubleDouble

参考

一文带你理解Java中自动装箱和拆箱