一.引言
小伙伴们应该都知道,只要我们写代码,必然就会有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 {
@Test
public 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考虑到这个范围内的整数出现的概率比较高】,则会直接从缓存数组中获取对象!也就是说这个范围内,无论代码中使用多少次,获取的都是同一个数据对象,而超出这个范围则每次都是创建新的对象。
会有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方法比较
代码修改后的运行结果:
这样两个商品都匹配到了对应的类别信息【问题解决!!!】
五. 小结
这个问题如果只是单纯的看代码,我们很难发现问题所在。其实在开发中,我们经常会碰到类似的问题。我们认为某段代码执行的逻辑应该是这样的,但实际上因为我们忽略了底层的一些细小的机制或对原理了解的不透彻,造成程序运行时得到了一个我们所不期望的结果【关键还不能解释为什么会这样】。因此壹哥希望大家在平时的学习和工作过程中,对知识点一定要注重原理注重细节,这样才能尽量避免在编写程序时出现一些错误的使用,或者尽可能少的出现一些我们无法解释的逻辑错误。
标签:等值,对象,private,比较,new,类别,Integer,BUG From: https://blog.51cto.com/u_7044146/5966096