2026/7/27 17:03:42

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

`==`、`equals` 和 Integer 缓存:为什么 `128 == 128` 是 false 前言先看一段能让人怀疑人生的代码Integera127,b127;System.out.println(ab);// trueIntegerc128,d128;System.out.println(cd);// false同样的写法只是把数值从127改成128的结果就从true翻成了false。第一次见到的人几乎都会愣一下这不科学。其实一点都不玄。背后是两个知识点的叠加比的到底是什么以及Integer有个-128~127的缓存。这篇文章把这两点讲透看完你不仅能解释 127/128还能躲开一整类包装类型比较的坑。环境说明本文基于 JDK 8。一、先复现把几种情况放一起现象更清楚publicclassIntegerEqualsDemo{publicstaticvoidmain(String[]args){Integera127,b127;System.out.println(127 127 : (ab));// trueIntegerc128,d128;System.out.println(128 128 : (cd));// falseIntegerenewInteger(127),fnewInteger(127);System.out.println(new 127 : (ef));// falseSystem.out.println(equals : c.equals(d));// true}}输出127 127 : true 128 128 : false new 127 : false equals : true三个反直觉的点127 127是true128 128却是false连new Integer(127) new Integer(127)都是false——明明值一样但只要改用equals全都是true。问题就一个到底在比什么为什么它对Integer这么不讲道理二、根因2.1比引用equals比内容这是总纲记住这一句能解决一大半问题对基本类型int、long、double…比的是值。对引用类型所有对象包括Integer比的是引用地址——也就是是不是同一个对象而不是值相不相等。Integer是对象所以c d问的其实是c和d是不是指向同一个对象而不是它们的值相不相等。new Integer(127) new Integer(127)为false就是这个道理new了两次是两个不同的对象地址自然不同哪怕值都是 127。而equals被Integer重写成了比较内部的int值所以只要值相等就返回true。那问题来了c、d又没new只是Integer c 128;为什么也是两个对象这就要说到自动装箱。2.2 自动装箱Integer a 127背后发生了什么Integer a 127;这行等号右边是int左边是Integer编译器会自动帮你装箱实际编译成IntegeraInteger.valueOf(127);注意是Integer.valueOf(127)不是new Integer(127)。这个区别是全部谜题的钥匙。2.3IntegerCache-128~127返回同一个缓存对象看Integer.valueOf的源码JDK 8publicstaticIntegervalueOf(inti){if(iIntegerCache.lowiIntegerCache.high)returnIntegerCache.cache[i(-IntegerCache.low)];// 命中缓存返回同一个对象returnnewInteger(i);// 超出范围new 新对象}Integer内部维护了一个静态缓存IntegerCache默认缓存-128到127这 256 个整数对应的Integer对象。逻辑很直白值在-128~127之间直接返回缓存里同一个对象超出这个范围new一个新对象。到这里127/128 的反转就彻底解释清楚了Integer a 127, b 127;→ 两次valueOf(127)都命中缓存返回的是同一个对象a b自然trueInteger c 128, d 128;→ 128 超出缓存范围两次各new一个新对象c d就是false。而new Integer(127)是显式new绕过了缓存所以哪怕在 127 也照样是两个对象为false。一张图看清valueOf的分支和缓存范围三、正解结论很简单包装类型比较值一律用equals别用。Integerc128,d128;// 有坑比的是对象地址结果随数值大小变化System.out.println(cd);// 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 判断。同类陷阱Long、Short、Byte、Character都有类似缓存Double、Float没有缓存浮点值域连续缓存无意义。另外String有字符串常量池也有类似的坑——都是引用比较惹的祸。四、常见误区与面试高频问答Q为什么缓存范围偏偏是-128~127能改吗这是一个字节byte能表示的范围也是实践中小整数最常用的区间缓存它们收益最高。上界可以通过 JVM 参数-XX:AutoBoxCacheMaxsize调大下界固定-128。调大后原本128 128为false的结果可能变成true——这也从侧面证明了它就是缓存在起作用。Q基本类型int之间用有问题吗没有。基本类型的比的就是值128 128两个int永远是true。坑只出现在包装类型上。QInteger和int用比会怎样Integerc128;intx128;System.out.println(cx);// true当一边是基本类型、一边是包装类型时包装类型会自动拆箱成基本类型于是比的是值结果反而正常了。也就是说Integer int是安全的Integer Integer才有坑。Q既然equals更可靠是不是就没用了不是。判断是不是同一个对象比如判断单例、判断引用是否被重新赋值就得用。和equals是两个不同的问题问是不是同一个equals问值是不是相等。选哪个取决于你要问什么。Q为什么阿里手册强制包装类之间比较用equals正是因为本文这个坑。用比较包装类结果会随数值是否落在缓存范围而变化——127对、128错这种时对时错的 bug 极难排查还很容易在测试时用小数据蒙混过关、上线用大数据翻车。统一用equals就彻底避开了。总结128 128为false不是 JVM 有 bug而是两个机制叠加的必然结果对引用类型比的是对象地址是不是同一个对象不是值equals才比值。Integer a 127会自动装箱成Integer.valueOf(127)而valueOf对-128~127返回缓存的同一个对象超出范围则new新对象。所以127命中缓存是同一个对象为true128超范围是两个对象为falsenew Integer绕过缓存永远是新对象。一句话记忆比地址、equals比值Integer只缓存-128~127超了就是两个对象——包装类型比较永远用equals。