对Integer进行等值比较时踩到的一个坑

2023-10-22 13:20
文章标签 进行 比较 integer 等值

本文主要是介绍对Integer进行等值比较时踩到的一个坑,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

一.引言

小伙伴们应该都知道,只要我们写代码,必然就会有BUG的存在。所以解决BUG的过程会伴随程序员的一生,这就是一个无解的常态。在平时的学习和工作过程中,我们需要通过不断地实践和总结,从而形成一套属于自己的解决处理BUG的成熟方案。之前壹哥看到过论坛中有人这样评价一个程序员的水平:只会写代码不会解BUG,只能算是一个业余的程序员;会写代码又能解决一般的BUG,可以称为是一个初级的程序员;会写代码还能解决复杂的BUG,才算是一个高级的软件工程师。这话说的尽管不完全正确,但也确实有一定的道理,这充分体现了BUG解决能力对一个软件工程师来说,具有能否在IT行业立足并长久发展下去的重要性。从这点来说壹哥还是非常认同的,毕竟解决BUG的过程就是一个开发人员w逐步成长并走向辉煌的过程。

既然BUG是开发中无法避免的问题,那我们就没必要在心理上排斥它,而应该学会在学习和工作中和BUG和谐共存的本领。有些BUG出现后会抛出具体的异常信息,而有些BUG则隐藏的比较深,属于是逻辑上的错误,甚至还会出现一些只有通过更换版本或者重启电脑才能得到解决的问题。当然,也有很大一部分BUG是因为编程人员自身粗心所导致的。壹哥认为只要保持一颗好的心态和足够的耐心,再结合调试工具、查看源码或从百度上查找等手段,大部分问题都是可以迎刃而解的。而且时间长了,还能在这个过程中不断的积累一些经验,可以更好的让我们去应对未来程序中出现的问题。

下面壹哥就给各位小伙伴分享一个我在辅导学员时碰到的奇葩问题,希望能给大家带来一些启示。

二.BUG重现

2.1 相关实体类

这里壹哥先编写如下实体类。

/*** 商品类*/
@Data
@AllArgsConstructor
@NoArgsConstructor
@ToString
public class Product {//商品编号private Integer proId;//商品名称private String proName;//库存数量private Integer quantity;//商品价格private Integer price;//类别编号private  Integer cateId;//商品对应的类别对象private  Category category;public Product(Integer proId, String proName, Integer quantity, Integer price, Integer cateId) {this.proId = proId;this.proName = proName;this.quantity = quantity;this.price = price;this.cateId = cateId;}
}
/*** 商品类别*/
@Data
@AllArgsConstructor
@NoArgsConstructor
@ToString
public class Category {//类别编号private Integer cateId;//类别名称private String cateName;
}

2.2 业务代码

然后在如下代码中进行具体的业务实现。

public class ServiceTest {@Testpublic void test() throws  Exception{//模拟从数据库中查询到的所有商品类别信息List<Category> cateList=Arrays.asList(new Category(12,"电脑"),new Category(56,"书籍"),new Category(519,"手机"));//模拟从数据库中查询到的部分商品信息List<Product> proList=new ArrayList<>();proList.add(new Product(1,"小米手机",100,2500,519));proList.add(new Product(2,"苹果电脑",200,8900,12));//通过双重循环设置每件商品对应的类别对象信息for (Product product : proList) {for (Category category : cateList) {if(product.getCateId().equals(category.getCateId())){product.setCategory(category);break;}}}//打印设置类别对象信息后的所有商品信息proList.forEach(p-> System.out.println(p));}
}

三. 执行结果及分析

3.1 执行结果

我们把上面的代码运行起来,结果就出现了如下异常:

3.2 结果分析

相信看到这里,很多小伙伴就有疑问了,明明测试数据中是有类别编号为519对应类别信息的,为什么在循环比较判断中没有匹配到呢?

通过IDE调试可以发现,在将1号商品小米手机的类别编号和所有类别的编号进行等值比较时,确实都没有进入到if语句体中。这就很奇怪了,为啥另一个商品又能找到对应的类别对象信息呢???

最后通过查看Integer包装类的源码我们发现,Integer对象在创建时如果值在-128~127范围内【JDK考虑到这个范围内的整数出现的概率比较高】,则会直接从缓存数组中获取对象!也就是说这个范围内,无论代码中使用多少次,获取的都是同一个数据对象,而超出这个范围则每次都是创建新的对象。

 而在这段源码中,我们可以看到其内部有一个if判断,根据判断结果的不同,会有2种不同的方式得到Integer对象:当arg大于等于-128且小于等于127时,则直接从缓存中返回一个已经存在的对象;如果参数的值不在这个范围内,则new一个Integer对象返回,要么new Integer,要么从int常量池中获取其中我们看到了一个IntegerCache缓存类,那么这个IntegerCache到底是什么呢? 我们看看IntegerCache的源码如下:

之前我们构建 Integer 对象的传统方式是直接调用构造器,会直接 new 一个Integer对象。但是根据实践,我们发现大部分数据操作都是集中在有限的、较小的数值范围内,因而在 JDK 5 中新增了一个静态工厂方法 valueOf(int i),当我们进行Integer i=xxx 赋值操作时,Java内部会调用执行这个valueOf()实现自动装箱。而在调用valueOf()方法时,其内部会利用缓存机制,对取值在-128~127之间的int值进行缓存操作,这是在 JDK 5 之后做的一个可以明显改善性能的提升按照 Javadoc,这个缓存机制默认会缓存在 -128 到 127 之间的值。 

JVM会自动维护八种基本类型的常量池,int常量池的初始化取值范围就是-128~127,当我们进行Integer i=127 赋值操作时,内部会通过调用valueOf()方法进行自动装箱操作,从而执行上文提到的缓存机制,即自动装箱时会从常量池中进行取值。而当Integer i=200时,200并不在常量池范围内,所以在自动装箱过程中需new Integer(200),所以两个对象x和y的地址不一样。

四.解决方式

我们知道,==比较运算符对两个JAVA对象进行相等比较,比较的是这两个对象的地址。根据上面的源码,我们不难得出Integer(12)和Integer(12)在内存中是同一个对象,因此比较的地址是相等的;而Integer(519)和Integer(519)在内存中是两个不同的对象【创建了两次,地址不同】,因此==比较的结果为不等,而我们的这个业务是希望按照它们的值进行是否相等的比较。

解决方法:将==比较运算替换为调用对象的equals方法比较

 代码修改后的运行结果:

 这样两个商品都匹配到了对应的类别信息【问题解决!!!】

五. 小结

这个问题如果只是单纯的看代码,我们很难发现问题所在。其实在开发中,我们经常会碰到类似的问题。我们认为某段代码执行的逻辑应该是这样的,但实际上因为我们忽略了底层的一些细小的机制或对原理了解的不透彻,造成程序运行时得到了一个我们所不期望的结果【关键还不能解释为什么会这样】。因此壹哥希望大家在平时的学习和工作过程中,对知识点一定要注重原理注重细节,这样才能尽量避免在编写程序时出现一些错误的使用,或者尽可能少的出现一些我们无法解释的逻辑错误。

这篇关于对Integer进行等值比较时踩到的一个坑的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/261700

相关文章

Python进行word模板内容替换的实现示例

《Python进行word模板内容替换的实现示例》本文介绍了使用Python自动化处理Word模板文档的常用方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友... 目录技术背景与需求场景核心工具库介绍1.获取你的word模板内容2.正常文本内容的替换3.表格内容的

Git进行版本控制的实战指南

《Git进行版本控制的实战指南》Git是一种分布式版本控制系统,广泛应用于软件开发中,它可以记录和管理项目的历史修改,并支持多人协作开发,通过Git,开发者可以轻松地跟踪代码变更、合并分支、回退版本等... 目录一、Git核心概念解析二、环境搭建与配置1. 安装Git(Windows示例)2. 基础配置(必

JavaScript中比较两个数组是否有相同元素(交集)的三种常用方法

《JavaScript中比较两个数组是否有相同元素(交集)的三种常用方法》:本文主要介绍JavaScript中比较两个数组是否有相同元素(交集)的三种常用方法,每种方法结合实例代码给大家介绍的非常... 目录引言:为什么"相等"判断如此重要?方法1:使用some()+includes()(适合小数组)方法2

Nginx中配置使用非默认80端口进行服务的完整指南

《Nginx中配置使用非默认80端口进行服务的完整指南》在实际生产环境中,我们经常需要将Nginx配置在其他端口上运行,本文将详细介绍如何在Nginx中配置使用非默认端口进行服务,希望对大家有所帮助... 目录一、为什么需要使用非默认端口二、配置Nginx使用非默认端口的基本方法2.1 修改listen指令

MySQL按时间维度对亿级数据表进行平滑分表

《MySQL按时间维度对亿级数据表进行平滑分表》本文将以一个真实的4亿数据表分表案例为基础,详细介绍如何在不影响线上业务的情况下,完成按时间维度分表的完整过程,感兴趣的小伙伴可以了解一下... 目录引言一、为什么我们需要分表1.1 单表数据量过大的问题1.2 分表方案选型二、分表前的准备工作2.1 数据评估

Python如何实现高效的文件/目录比较

《Python如何实现高效的文件/目录比较》在系统维护、数据同步或版本控制场景中,我们经常需要比较两个目录的差异,本文将分享一下如何用Python实现高效的文件/目录比较,并灵活处理排除规则,希望对大... 目录案例一:基础目录比较与排除实现案例二:高性能大文件比较案例三:跨平台路径处理案例四:可视化差异报

MySQL进行分片合并的实现步骤

《MySQL进行分片合并的实现步骤》分片合并是指在分布式数据库系统中,将不同分片上的查询结果进行整合,以获得完整的查询结果,下面就来具体介绍一下,感兴趣的可以了解一下... 目录环境准备项目依赖数据源配置分片上下文分片查询和合并代码实现1. 查询单条记录2. 跨分片查询和合并测试结论分片合并(Shardin

SpringBoot结合Knife4j进行API分组授权管理配置详解

《SpringBoot结合Knife4j进行API分组授权管理配置详解》在现代的微服务架构中,API文档和授权管理是不可或缺的一部分,本文将介绍如何在SpringBoot应用中集成Knife4j,并进... 目录环境准备配置 Swagger配置 Swagger OpenAPI自定义 Swagger UI 底

基于Python Playwright进行前端性能测试的脚本实现

《基于PythonPlaywright进行前端性能测试的脚本实现》在当今Web应用开发中,性能优化是提升用户体验的关键因素之一,本文将介绍如何使用Playwright构建一个自动化性能测试工具,希望... 目录引言工具概述整体架构核心实现解析1. 浏览器初始化2. 性能数据收集3. 资源分析4. 关键性能指

Nginx进行平滑升级的实战指南(不中断服务版本更新)

《Nginx进行平滑升级的实战指南(不中断服务版本更新)》Nginx的平滑升级(也称为热升级)是一种在不停止服务的情况下更新Nginx版本或添加模块的方法,这种升级方式确保了服务的高可用性,避免了因升... 目录一.下载并编译新版Nginx1.下载解压2.编译二.替换可执行文件,并平滑升级1.替换可执行文件