`==`、`equals` 和 Integer 缓存:为什么 `128 == 128` 是 false

前言

先看一段能让人怀疑人生的代码:

Integer a = 127, b = 127;
System.out.println(a == b);   // true

Integer c = 128, d = 128;
System.out.println(c == d);   // false

同样的写法,只是把数值从 127 改成 128== 的结果就从 true 翻成了 false。第一次见到的人几乎都会愣一下:这不科学。

其实一点都不玄。背后是两个知识点的叠加:== 比的到底是什么,以及 Integer 有个 -128~127 的缓存。这篇文章把这两点讲透,看完你不仅能解释 127/128,还能躲开一整类"包装类型比较"的坑。

环境说明:本文基于 JDK 8。


一、先复现

把几种情况放一起,现象更清楚:

public class IntegerEqualsDemo {
    public static void main(String[] args) {
        Integer a = 127, b = 127;
        System.out.println("127 == 127 : " + (a == b));   // true

        Integer c = 128, d = 128;
        System.out.println("128 == 128 : " + (c == d));   // false

        Integer e = new Integer(127), f = new Integer(127);
        System.out.println("new 127   : " + (e == f));     // false

        System.out.println("equals    : " + c.equals(d));  // true
    }
}

输出:

127 == 127 : true
128 == 128 : false
new 127   : false
equals    : true

三个反直觉的点:

  • 127 == 127true128 == 128 却是 false
  • new Integer(127) == new Integer(127) 都是 false——明明值一样;
  • 但只要改用 equals,全都是 true

问题就一个:== 到底在比什么,为什么它对 Integer 这么"不讲道理"?


二、根因

2.1 == 比引用,equals 比内容

这是总纲,记住这一句能解决一大半问题:

  • 基本类型intlongdouble…),== 比的是
  • 引用类型(所有对象,包括 Integer),== 比的是引用地址——也就是"是不是同一个对象",而不是值相不相等。

Integer 是对象,所以 c == d 问的其实是"cd 是不是指向同一个对象",而不是"它们的值相不相等"。new Integer(127) == new Integer(127)false 就是这个道理:new 了两次,是两个不同的对象,地址自然不同,哪怕值都是 127。

equalsInteger 重写成了比较内部的 int,所以只要值相等就返回 true

那问题来了:cd 又没 new,只是 Integer c = 128;,为什么也是两个对象?这就要说到自动装箱。

2.2 自动装箱:Integer a = 127 背后发生了什么

Integer a = 127; 这行,等号右边是 int,左边是 Integer,编译器会自动帮你"装箱",实际编译成:

Integer a = Integer.valueOf(127);

注意,是 Integer.valueOf(127),不是 new Integer(127) 这个区别是全部谜题的钥匙。

2.3 IntegerCache-128~127 返回同一个缓存对象

Integer.valueOf 的源码(JDK 8):

public static Integer valueOf(int i) {
    if (i >= IntegerCache.low && i <= IntegerCache.high)
        return IntegerCache.cache[i + (-IntegerCache.low)];  // 命中缓存,返回同一个对象
    return new Integer(i);                                    // 超出范围,new 新对象
}

Integer 内部维护了一个静态缓存 IntegerCache,默认缓存 -128127 这 256 个整数对应的 Integer 对象。逻辑很直白:

  • 值在 -128~127 之间:直接返回缓存里同一个对象;
  • 超出这个范围:new 一个对象。

到这里,127/128 的反转就彻底解释清楚了:

  • Integer a = 127, b = 127; → 两次 valueOf(127) 都命中缓存,返回的是同一个对象a == b 自然 true
  • Integer c = 128, d = 128; → 128 超出缓存范围,两次各 new 一个新对象c == d 就是 false

new Integer(127) 是显式 new绕过了缓存,所以哪怕在 127 也照样是两个对象,==false

在这里插入图片描述

一张图看清 valueOf 的分支和缓存范围:

在这里插入图片描述


三、正解

结论很简单:包装类型比较值,一律用 equals,别用 ==

Integer c = 128, d = 128;

// 有坑:比的是对象地址,结果随数值大小变化
System.out.println(c == d);          // false

// 正解:比的是值,永远可靠
System.out.println(c.equals(d));     // true

几个补充:

  • 想用 == 也行,但要先拆箱成基本类型c.intValue() == d.intValue(),或者让其中一边是 int(见下面第四节)。
  • 注意 equals 的空指针:如果变量可能为 nullc.equals(d) 会 NPE。用 java.util.Objects.equals(c, d) 更安全,它内部做了 null 判断。
  • 同类陷阱LongShortByteCharacter 都有类似缓存,DoubleFloat 没有缓存(浮点值域连续,缓存无意义)。另外 String 有字符串常量池,== 也有类似的坑——都是"引用比较"惹的祸。

四、常见误区与面试高频问答

Q:为什么缓存范围偏偏是 -128~127?能改吗?

这是一个字节(byte)能表示的范围,也是实践中小整数最常用的区间,缓存它们收益最高。上界可以通过 JVM 参数 -XX:AutoBoxCacheMax=<size> 调大(下界固定 -128)。调大后,原本 128 == 128false 的结果可能变成 true——这也从侧面证明了它就是缓存在起作用。

Q:基本类型 int 之间用 == 有问题吗?

没有。基本类型的 == 比的就是值,128 == 128(两个 int)永远是 true。坑只出现在包装类型上。

Q:Integerint== 比会怎样?

Integer c = 128;
int x = 128;
System.out.println(c == x);   // true

== 一边是基本类型、一边是包装类型时,包装类型会自动拆箱成基本类型,于是比的是,结果反而"正常"了。也就是说 Integer == int 是安全的,Integer == Integer 才有坑。

Q:既然 equals 更可靠,== 是不是就没用了?

不是。判断"是不是同一个对象"(比如判断单例、判断引用是否被重新赋值)就得用 ====equals 是两个不同的问题:== 问"是不是同一个",equals 问"值是不是相等"。选哪个取决于你要问什么。

Q:为什么阿里手册强制包装类之间比较用 equals

正是因为本文这个坑。用 == 比较包装类,结果会随数值是否落在缓存范围而变化——127 对、128 错,这种"时对时错"的 bug 极难排查,还很容易在测试时用小数据蒙混过关、上线用大数据翻车。统一用 equals 就彻底避开了。


总结

128 == 128false,不是 JVM 有 bug,而是两个机制叠加的必然结果:

  • ==引用类型比的是对象地址(是不是同一个对象),不是值;equals 才比值。
  • Integer a = 127 会自动装箱成 Integer.valueOf(127),而 valueOf-128~127 返回缓存的同一个对象,超出范围则 new 新对象。
  • 所以 127 命中缓存是同一个对象(==true),128 超范围是两个对象(==false);new Integer 绕过缓存,永远是新对象。

一句话记忆: == 比地址、equals 比值;Integer 只缓存 -128~127,超了就是两个对象——包装类型比较,永远用 equals

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Leighteen

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值