Java Integer 判等陷阱深度复盘:为什么 168≠168 差点引发生产事故?
前阵子组里来了个新人,我分配给他一个看起来非常简单的任务。
我们团队固定在每周四上线发版,按照惯例,上线前两天会进行集体代码评审。那次评审也顺利通过了——功能本身很直接,大家都没有太细看。
但从业这么多年,我一直保持着上线前再做一次独立代码审查的习惯,这个习惯确实救过不少次场。
就在最后一遍过代码的时候,IDEA 突然弹出一个智能提示,我当时心里就“咯噔”一下:

我心里很清楚,如果这次二次复查没有发现这个问题,后续的麻烦绝不是小事。正是这次经历促成了这篇文章的整理。
一个“简单”代码段的反直觉表现
各位 Java 开发者不妨先停下想一想:执行下面这段代码后,控制台会输出“不相等”吗?
public static void main(String[] args) {
Integer total = 168;
Integer count = 168;
if (total != count) {
System.out.println("不相等!!!");
} else {
System.out.println("相等!");
}
}
很多人第一反应就是:两个变量的值都是 168,那结果肯定是“相等!”才对。
然而真实的运行结果却是:

它直接打印了“不相等”。
这种现象背后的根本原因
这个问题其实反映了 Java 中一个常见的认知模糊点,不只是新手,一些有几年经验的开发者也可能会掉进去:
int是基本数据类型,使用==比较的是数值本身;Integer是包装类,属于引用类型,==比较的是对象的内存地址。
也就是说,即便两个 Integer 对象里面保存的数值一模一样,只要它们不是同一个实例,== 就会返回 false。
这个坑正好解释了上面代码中 total != count 成立的原因——两个 168 的 Integer 对象在内存里指向不同的地址。
更让人困惑的“相等”情况
再来看另一段看似几乎一样的代码:
Integer total2 = 100;
Integer count2 = 100;
if (total2 != count2) {
System.out.println("不相等!");
} else {
System.out.println("相等!");
}
猜猜这次会打印什么?

结果输出的是 “相等!”。
看到这里是不是更疑惑了?
同样是赋值并用 == 比较,为什么 168 不相等,100 却变得相等了?
幕后的“黑手”:Integer 缓存机制
Java 在设计 Integer 时,对 -128 到 127 这个区间的数字做了内部缓存(上限可通过 JVM 参数调整)。
当我们通过自动装箱或者 Integer.valueOf() 获取在这个范围内的 Integer 对象时,JVM 会直接返回缓存池中的同一个对象,而不是新建一个。一旦超过这个范围,每一次装箱都会生成全新的实例。
这样一来:
Integer a = 100;和Integer b = 100;实际上指向缓存中的同一个对象,因此==结果为true;Integer a = 168;和Integer b = 168;已经超出缓存范围,各自都是不同的新对象,所以==结果为false。
另外,如果使用了 new Integer(100) 这种显式构造方式,就算数值在缓存区间内,也会强制创建新对象,此时 == 必然返回 false。
正确的 Integer 值比较方式
✅ 推荐方案一:使用 Objects.equals()
这种方式可以自动规避 null 引发的空指针异常,是安全性最高的选择。
if (!Objects.equals(total, count)) {
// 不相等逻辑
}
✅ 推荐方案二:先拆箱成 int 再比较
适合确保变量一定不为 null 的场景,写法也比较直观。
if (total.intValue() != count.intValue()) {
// 不相等逻辑
}
// 或者利用自动拆箱
int t = total;
int c = count;
if (t != c) { ... }
✅ 可选方案:使用 compareTo()
适用于需要在判断相等性的同时比较大小,且变量肯定不为 null 的情况。
if (total.compareTo(count) != 0) {
// 不相等逻辑
}
复盘总结
这个小陷阱看起来并不复杂,但一旦泄漏到线上生产环境中,造成的后果往往让人措手不及。
这次经历再次印证了三个关键点:
Integer是对象类型,==对比的是内存地址,不是数值;- 缓存机制仅在 [-128, 127] 范围内自动生效,超限或显式
new都会产生新对象; - 进行
Integer值相等性判断时,应当优先使用Objects.equals()或先拆箱再比较。
基础不够牢固,再简单的代码也可能变成巨大的隐患。很多线上事故的源头,恰恰就藏在这些我们以为“完全不会出错”的地方。