为什么两个看似相等的 Integer 却不相等?一次诡异的缓存折扣商品 BUG 排查

🌷 古之立大事者,不惟有超世之才,亦必有坚忍不拔之志

🎐 个人CSND主页------Micro麦可乐的博客

🐥《Docker实操教程》专栏以最新的Centos版本为基础进行Docker实操教程,入门到实战

🌺《RabbitMQ》专栏19年编写主要介绍使用JAVA开发RabbitMQ的系列教程,从基础知识到项目实战

🌸《设计模式》专栏以实际的生活场景为案例进行讲解,让大家对设计模式有一个更清晰的理解

🌛《开源项目》本专栏主要介绍目前热门的开源项目,带大家快速了解并轻松上手使用

🍎 《前端技术》专栏以实战为主介绍日常开发中前端应用的一些功能以及技巧,均附有完整的代码示例

✨《开发技巧》本专栏包含了各种系统的设计原理以及注意事项,并分享一些日常开发的功能小技巧

💕《Jenkins实战》专栏主要介绍Jenkins+Docker的实战教程,让你快速掌握项目CI/CD,是2024年最新的实战教程

🌞《Spring Boot》专栏主要介绍我们日常工作项目中经常应用到的功能以及技巧,代码样例完整

👍《Spring Security》专栏中我们将逐步深入Spring Security的各个技术细节,带你从入门到精通,全面掌握这一安全技术
如果文章能够给大家带来一定的帮助!欢迎关注、评论互动~

为什么两个看似相等的 Integer 却不相等?一次诡异的缓存折扣商品 BUG 排查

BUG 场景复现

我们在今天的代码审查中,我发现了新来的同事小龙写了这样一段逻辑,用于判断当前商品是否为后台设置的折扣商品:

java 复制代码
Integer productId = 100;
Integer discountId = getDiscountIdFromCache(); // 缓存中读取的折扣ID

if (productId == discountId) {
    System.out.println("匹配折扣商品");
} else {
    System.out.println("不是折扣商品");
}

乍一看是不是觉得没什么问题,当时我就问小龙这里判断是不是存在问题?小龙信誓旦旦说没有问题,我让他试试比较一个产品ID大于128的相同ID比较!瞬间他懵了,明明是相等的两个产品ID数,为什么判断居然是false

这时我拍了拍小龙的肩膀,让过来人跟你讲讲为什么会出现这样的问题吧!

问题分析

造成这个问题的关键点在于 Java 中的 == 对于对象比较的是引用地址,而不是值。

说明
Integer 是对象,而不是基本类型 int
== 比较对象时,判断的是内存地址是否相同。
Integer 有一个特殊的 缓存机制:默认缓存范围为 [-128, 127]

通过上述说明那么我们就可以理解:

  • 当ID值在-128到127之间时,使用==比较会返回true
  • 当ID值超出这个范围时,使用==比较会返回false(因为是不同的对象实例)

如下面的例子

java 复制代码
Integer a = 100;
Integer b = 100;
System.out.println(a == b); // true,因为命中缓存

Integer x = 200;
Integer y = 200;
System.out.println(x == y); // false,因为超出缓存范围,创建了两个对象

解决方案:使用正确比较方法

要比较两个 Integer 的值,正确的方式是:

使用 equals 方法

java 复制代码
// 还是上述的判断折扣商品例子
Integer productId = 100;
Integer discountId = getDiscountIdFromCache(); 

if (productId.equals(discountId)) {
    System.out.println("匹配折扣商品");
}

使用 intValue() 转换为基本类型

java 复制代码
if (productId.intValue() == discountId.intValue()) {
    System.out.println("匹配折扣商品");
}

直接定义为基本类型 int

在系统设计之初,如果已经确认ID是数字,可以直接使用基本类型 int , 避免对象包装带来的性能和判等问题

java 复制代码
int productId = 200;
int discountId = getDiscountIdFromCache();
if (productId == discountId) {
    System.out.println("匹配折扣商品");
}

最后的建议

为了避免这类问题的发生,我们需要定义个一个规范,避免后续出现这类问题:

  • 始终使用equals()比较对象内容 :对于所有包装类(Integer、Long、Double等)的比较,都要求使用equals比较
  • 注意空指针问题 :使用 equals 时,应把常量写在前面,如 Objects.equals(a, b) "200".equals(str)
  • 开发工具配置检查插件:可配置一些代码规范、代码检查的工具来检测这类问题

总结

这次折扣商品匹配 BUG 的根源在于 把 == 用于 Integer 对象比较。

由于 JavaInteger 缓存机制,小整数可以"碰巧"相等,而大整数则不会,从而导致了诡异的 BUG。

如果你在实践过程中有任何疑问或更好的扩展思路,欢迎在评论区留言,最后希望大家 一键三连 给博主一点点鼓励!


相关推荐
一嘴一个橘子17 分钟前
spring-aop 的 基础使用 - 4 - 环绕通知 @Around
java
小毅&Nora34 分钟前
【Java线程安全实战】⑨ CompletableFuture的高级用法:从基础到高阶,结合虚拟线程
java·线程安全·虚拟线程
冰冰菜的扣jio34 分钟前
Redis缓存中三大问题——穿透、击穿、雪崩
java·redis·缓存
小璐猪头1 小时前
专为 Spring Boot 设计的 Elasticsearch 日志收集 Starter
java
ps酷教程1 小时前
HttpPostRequestDecoder源码浅析
java·http·netty
闲人编程1 小时前
消息通知系统实现:构建高可用、可扩展的企业级通知服务
java·服务器·网络·python·消息队列·异步处理·分发器
栈与堆1 小时前
LeetCode-1-两数之和
java·数据结构·后端·python·算法·leetcode·rust
oMcLin1 小时前
如何在 AlmaLinux 9 上配置并优化 Redis 集群,支持高并发的实时数据缓存与快速查询?
数据库·redis·缓存
OC溥哥9992 小时前
Paper MinecraftV3.0重大更新(下界更新)我的世界C++2D版本隆重推出,拷贝即玩!
java·c++·算法
星火开发设计2 小时前
C++ map 全面解析与实战指南
java·数据结构·c++·学习·算法·map·知识