前言
先看一段能让人怀疑人生的代码:
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 == 127是true,128 == 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,编译器会自动帮你"装箱",实际编译成:
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,默认缓存 -128 到 127 这 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的空指针:如果变量可能为null,c.equals(d)会 NPE。用java.util.Objects.equals(c, d)更安全,它内部做了 null 判断。 - 同类陷阱:
Long、Short、Byte、Character都有类似缓存,Double、Float没有缓存(浮点值域连续,缓存无意义)。另外String有字符串常量池,==也有类似的坑——都是"引用比较"惹的祸。
四、常见误区与面试高频问答
Q:为什么缓存范围偏偏是 -128~127?能改吗?
这是一个字节(byte)能表示的范围,也是实践中小整数最常用的区间,缓存它们收益最高。上界可以通过 JVM 参数 -XX:AutoBoxCacheMax=<size> 调大(下界固定 -128)。调大后,原本 128 == 128 为 false 的结果可能变成 true——这也从侧面证明了它就是缓存在起作用。
Q:基本类型 int 之间用 == 有问题吗?
没有。基本类型的 == 比的就是值,128 == 128(两个 int)永远是 true。坑只出现在包装类型上。
Q:Integer 和 int 用 == 比会怎样?
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 == 128 为 false,不是 JVM 有 bug,而是两个机制叠加的必然结果:
==对引用类型比的是对象地址(是不是同一个对象),不是值;equals才比值。Integer a = 127会自动装箱成Integer.valueOf(127),而valueOf对-128~127返回缓存的同一个对象,超出范围则new新对象。- 所以
127命中缓存是同一个对象(==为true),128超范围是两个对象(==为false);new Integer绕过缓存,永远是新对象。
一句话记忆: == 比地址、equals 比值;Integer 只缓存 -128~127,超了就是两个对象——包装类型比较,永远用 equals。

3029

被折叠的 条评论
为什么被折叠?



