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

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

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

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

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

类型/类型 int new Integer Integer 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对象。

总结如下:

基本类型 包装类 缓存池
byte Byte [-128, 127]
short Short [-128, 127]
int Integer [-128, 127]
long Long [-128, 127]
char Character [-128, 127]
boolean Boolean [true, false]
float Float
double Double

参考

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,039评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,223评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,916评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,009评论 1 291
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,030评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,011评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,934评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,754评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,202评论 1 309
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,433评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,590评论 1 346
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,321评论 5 342
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,917评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,568评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,738评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,583评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,482评论 2 352

推荐阅读更多精彩内容