java开发经常遇到的bug(代码片段)

xinyuan_java xinyuan_java     2022-12-30     760

关键词:

1. 用==号比较的坑

不知道你在项目中有没有见过,有些同事对Integer类型的两个参数使用==号比较是否相等?

反正我见过的,那么这种用法对吗?

我的回答是看具体场景,不能说一定对,或不对。

有些状态字段,比如:orderStatus有:-1(未下单),0(已下单),1(已支付),2(已完成),3(取消),5种状态。

这时如果用==判断是否相等:

 
Integer orderStatus1 = new Integer(1);
Integer orderStatus2 = new Integer(1);
System.out.println(orderStatus1 == orderStatus2);

返回结果会是true吗?

答案:是false

有些同学可能会反驳,Integer中不是有范围是:-128-127的缓存吗?

为什么是false?

先看看Integer的构造方法:

它其实并没有用到缓存

那么缓存是在哪里用的?

答案在valueOf方法中:

如果上面的判断改成这样:

 
String orderStatus1 = new String("1");
String orderStatus2 = new String("1");
System.out.println(Integer.valueOf(orderStatus1) == Integer.valueOf(orderStatus2));

返回结果会是true吗?

答案:还真是true

我们要养成良好编码习惯,尽量少用==判断两个Integer类型数据是否相等,只有在上述非常特殊的场景下才相等。

而应该改成使用equals方法判断:

 
Integer orderStatus1 = new Integer(1);
Integer orderStatus2 = new Integer(1);
System.out.println(orderStatus1.equals(orderStatus2));

运行结果为true

2. Objects.equals的坑

假设现在有这样一个需求:判断当前登录的用户,如果是我们指定的系统管理员,则发送一封邮件。系统管理员没有特殊的字段标识,他的用户id=888,在开发、测试、生产环境中该值都是一样的。

这个需求真的太容易实现了:

 
UserInfo userInfo = CurrentUser.getUserInfo();

if(Objects.isNull(userInfo)) 
   log.info("请先登录");
   return;


if(Objects.equals(userInfo.getId(),888L)) 
   sendEmail(userInfo):

从当前登录用户的上下文中获取用户信息,判断一下,如果用户信息为空,则直接返回。

如果获取到的用户信息不为空,接下来判断用户id是否等于888。

  • 如果等于888,则发送邮件。

  • 如果不等于888,则啥事也不干。

当我们用id=888的系统管理员账号登录之后,做了相关操作,满怀期待的准备收邮件的时候,却发现收了个寂寞。

后来,发现UserInfo类是这样定义的:

 
@Data
public class UserInfo 
    private Integer id;
    private String name;
    private Integer age;
    private String address;

此时,有些小伙伴可能会说:没看出什么问题呀。

但我要说的是这个代码确实有问题。

什么问题呢?

下面我们重点看看它的equals方法:

 
public static boolean equals(Object a, Object b) 
    return (a == b) || (a != null && a.equals(b));

equals方法的判断逻辑如下:

  1. 该方法先判断对象a和b的引用是否相等,如果相等则直接返回true。

  2. 如果引用不相等,则判断a是否为空,如果a为空则返回false。

  3. 如果a不为空,调用对象的equals方法进一步判断值是否相等。

这就要从Integerequals方法说起来了。

它的equals方法具体代码如下:

 
public boolean equals(Object obj) 
    if (obj instanceof Integer) 
        return value == ((Integer)obj).intValue();
    
    return false;

先判断参数obj是否是Integer类型,如果不是,则直接返回false。如果是Integer类型,再进一步判断int值是否相等。

而上面这个例子中b是long类型,所以Integer的equals方法直接返回了false。

也就是说,如果调用了Integer的equals方法,必须要求入参也是Integer类型,否则该方法会直接返回false。

除此之外,还有Byte、Short、Double、Float、Boolean和Character也有类似的equals方法判断逻辑。

常见的坑有:

  1. Long类型和Integer类型比较,比如:用户id的场景。

  2. Byte类型和Integer类型比较,比如:状态判断的场景。

  3. Double类型和Integer类型比较,比如:金额为0的判断场景。

3. BigDecimal的坑

通常我们会把一些小数类型的字段(比如:金额),定义成BigDecimal,而不是Double,避免丢失精度问题。

使用Double时可能会有这种场景:

 
double amount1 = 0.02;
double amount2 = 0.03;
System.out.println(amount2 - amount1);

正常情况下预计amount2 - amount1应该等于0.01

但是执行结果,却为:

 
0.009999999999999998

实际结果小于预计结果。

Double类型的两个参数相减会转换成二进制,因为Double有效位数为16位这就会出现存储小数位数不够的情况,这种情况下就会出现误差。

常识告诉我们使用BigDecimal能避免丢失精度。

但是使用BigDecimal能避免丢失精度吗?

答案是否定的。

为什么?

 
BigDecimal amount1 = new BigDecimal(0.02);
BigDecimal amount2 = new BigDecimal(0.03);
System.out.println(amount2.subtract(amount1));

这个例子中定义了两个BigDecimal类型参数,使用构造函数初始化数据,然后打印两个参数相减后的值。

结果:

 
0.0099999999999999984734433411404097569175064563751220703125

不科学呀,为啥还是丢失精度了?

JdkBigDecimal构造方法上有这样一段描述:

大致的意思是此构造函数的结果可能不可预测,可能会出现创建时为0.1,但实际是0.1000000000000000055511151231257827021181583404541015625的情况。

由此可见,使用BigDecimal构造函数初始化对象,也会丢失精度。

那么,如何才能不丢失精度呢?

 
BigDecimal amount1 = new BigDecimal(Double.toString(0.02));
BigDecimal amount2 = new BigDecimal(Double.toString(0.03));
System.out.println(amount2.subtract(amount1));

我们可以使用Double.toString方法,对double类型的小数进行转换,这样能保证精度不丢失。

其实,还有更好的办法:

 
BigDecimal amount1 = BigDecimal.valueOf(0.02);
BigDecimal amount2 = BigDecimal.valueOf(0.03);
System.out.println(amount2.subtract(amount1));

使用BigDecimal.valueOf方法初始化BigDecimal类型参数,也能保证精度不丢失。在新版的阿里巴巴开发手册中,也推荐使用这种方式创建BigDecimal参数。

4. Java8 filter的坑

对于Java8中的Stream用法,大家肯定再熟悉不过了。

我们通过对集合Stream操作,可以实现:遍历集合、过滤数据、排序、判断、转换集合等等,N多功能。

这里重点说说数据的过滤。

在没有Java8之前,我们过滤数据一般是这样做的:

 
public List<User> filterUser(List<User> userList) 
    if(CollectionUtils.isEmpty(userList)) 
        return Collections.emptyList();
    
    
    List<User> resultList = Lists.newArrayList();
    for(User user: userList) 
        if(user.getId() > 1000 && user.getAge() > 18) 
           resultList.add(user);
        
    
    return resultList;

通常需要另一个集合辅助完成这个功能。

但如果使用Java8的filter功能,代码会变得简洁很多,例如:

 
public List<User> filterUser(List<User> userList) 
    if(CollectionUtils.isEmpty(userList)) 
        return Collections.emptyList();
    
    
    return userList.stream()
    .filter(user -> user.getId() > 1000 && user.getAge() > 18)
    .collect(Collectors.toList());

代码简化了很多,完美。

但如果你对过滤后的数据,做修改了:

 
List<User> userList = queryUser();
List<User> filterList = filterUser(userList);
for(User user: filterList) 
   user.setName(user.getName() + "测试");


for(User user: userList) 
   System.out.println(user.getName());

你当时可能只是想修改过滤后的数据,但实际上,你会把元素数据一同修改了。

意不意外,惊不惊喜?

其根本原因是:过滤后的集合中,保存的是对象的引用,该引用只有一份数据。

也就是说,只要有一个地方,把该引用对象的成员变量的值,做修改了,其他地方也会同步修改。

如下图所示:

5. 自动拆箱的坑

Java5之后,提供了自动装箱自动拆箱的功能。

自动装箱是指:JDK会把基本类型,自动变成包装类型。

比如:

 
Integer integer = 1;

等价于:

 
Integer integer = new Integer(1);

而自动拆箱是指:JDK会把包装类型,自动转换成基本类型。

例如:

 
Integer integer = new Integer(2);
int sum = integer + 5;

等价于:

 
Integer integer = new Integer(2);
int sum = integer.intValue() + 5;

但实际工作中,我们在使用自动拆箱时,往往忘记了判空,导致出现NullPointerException异常。

5.1 运算

很多时候,我们需要对传入的数据进行计算,例如:

 
public class Test2 
    public static void main(String[] args) 
        System.out.println(add(new Integer(1), new Integer(2)));
    

    private static Integer add(Integer a, Integer b) 
        return a + b;
    

如果传入了null值:

 
System.out.println(add(null, new Integer(2)));

则会直接报错。

5.2 传参

有时候,我们定义的某个方法是基本类型,但实际上传入了包装类,比如:

 
public static void main(String[] args) 
    Integer a = new Integer(1);
    Integer b = null;
    System.out.println(add(a, b));


private static Integer add(int a, int b) 
    return a + b;

如果出现add方法报NullPointerException异常,你可能会懵逼,int类型怎么会出现空指针异常呢?

其实,这个问题出在:Integer类型的参数,其实际传入值为null,JDK字段拆箱,调用了它的intValue方法导致的问题。

6. replace的坑

很多时候我们在使用字符串时,想把字符串比如:ATYSDFA*Y中的字符A替换成字符B,第一个想到的可能是使用replace方法。

如果想把所有的A都替换成B,很显然可以用replaceAll方法,因为非常直观,光从方法名就能猜出它的用途。

那么问题来了:replace方法会替换所有匹配字符吗?

jdk的官方给出了答案。

该方法会替换每一个匹配的字符串。

既然replace和replaceAll都能替换所有匹配字符,那么他们有啥区别呢?

replace有两个重载的方法。

  • 其中一个方法的参数:char oldChar 和 char newChar,支持字符的替换。

 
source.replace('A', 'B')

  • 另一个方法的参数是:CharSequence target 和 CharSequence replacement,支持字符串的替换。

 
source.replace("A", "B")

replaceAll方法的参数是:String regex 和 String replacement,即基于正则表达式的替换。

例如对普通字符串进行替换:

 
source.replaceAll("A", "B")

使用正则表达替换(将*替换成C):

 
source.replaceAll("\\\\*", "C")

顺便说一下,将*替换成C使用replace方法也可以实现:

 
source.replace("*", "C")

小伙们看到看到二者的区别了没?使用replace方法无需对特殊字符进行转义。

不过,千万注意,切勿使用如下写法:

 
source.replace("\\\\*", "C")

这种写法会导致字符串无法替换。

还有个小问题,如果我只想替换第一个匹配的字符串该怎么办?

这时可以使用replaceFirst方法:

 
source.replaceFirst("A", "B")

说实话,这里内容都很基础,但越基础的东西,越容易大意失荆州,更容易踩坑。

对近期开发中遇到的有趣bug的思考

问题一:  第一个问题是对IDE过于依赖导致的bug,由于我经常使用Java和c++,之间的一些区别有时候就很容易忽略:  inta=0;intb=1;if(a=b){...}   在Java中,这样的判断eclipse是会报错的,因为a=b是赋值语句,所以返回值是对... 查看详情

java-定位排查bug(代码片段)

在开发过程中难免会遇到bug,理解bug的含义,定位bug的位置,对于解决bug至关重要!掌握高效的排错技巧,对于程序员来说必不可少。目录一、错误异常的分类二、常见报错信息及原因(持续更新中)... 查看详情

开发中经常遇到的javascript问题整理(超实用)(代码片段)

作者@chengyuming 原文地址:https://chengyuming.cn/views/basis/issue.html   获取一个月有多少天今天遇到一个需求,已知月份,得到这个月的第一天和最后一天作为查询条件查范围内的数据newDate(year,month,date,hrs,min,sec),n... 查看详情

开发中遇到的bug-propertyormethod“xxxx“isnotdefinedontheinstancebutreferencedduringrender.(代码片段)

method没加s加上就好了<body><divid="app"><button@click="sub">-</button><span>code</span><button@click="add">+</butt 查看详情

java开发最容易写的10个bug(代码片段)

...,没错,他说的好像就是我。。。。。。作为Java开发,我们在写代码的过程中难免会产生各种奇思妙想的bug,有些bug就挺让人无奈的,比如说各种空指针异常,在ArrayList的迭代中进行删除操作引发异常ÿ... 查看详情

h5开发移动端遇到的bug(代码片段)

之前开发过几个移动端的项目,经常会遇到莫名其妙的bug,现在有空就一一记录下,避免下次遇到时毫无头绪。H5在ios上把某些数字变色造成的原因:safari总会把长串数字识别为电话号码,文字变成蓝色,点击还会弹出菜单添加... 查看详情

微信小程序开发bug经验总结(代码片段)

摘要:常见的微信小程序BUG!小程序开发越来越热,开发中遇到各种各样的bug,在此总结了一些比较容易掉进去的坑分享给大家。1.newDate跨平台兼容性问题在Andriod使用newDate(“2018-05-3000:00:00”)木有问题,但是在ios下面识别不出... 查看详情

移动端经常遇到的小bug

1、video不能自动播放  (1)autoplay及js控制播放,仍然有部分设备不起作用  (2)$("html").one("touchstart",function(){       video.play();     })2、input输入框,软键盘弹出的时候会把底部的内容往上压缩,使得底部布局混乱 ... 查看详情

手机遇到性能bug怎么破?(代码片段)

...前手机SOC的性能越来越少,很多程序员在终端程序的开发过程中也不太注意性能方面的优化,尤其是不注意对齐和分支优化,但是这两种问题一旦出现所引发的问题,是非常非常隐蔽难查的,不过好在项目中... 查看详情

包管理原则(代码片段)

摘要坊间传闻javaweb开发人员写了那么多代码,但是其实一半代码都在处理NPE。总是在加班,却大部分时间都在处理包冲突,类加载不了的bug。这些问题总是让新老程序员都很抓狂,有很多的工具可以辅助我们解决这些问题(maven... 查看详情

测试人常见问题及解决方案合集,一键收藏...(代码片段)

前言测试工程师经常遇到的问题:开发的提测质量不佳,开始提测之后明明还有许多功能没有开发完整就提测,导致测试延期经常出现功能漏测的情况,无法保障产品质量经常出现明明测试过的功能,测试通... 查看详情

记录一次bug修复-entityframworksavechanges()失效(代码片段)

...解决步骤六、总结一、前言这是笔者在参与一个小型项目开发时所遇到的一个BUG,因为项目经验不足对EntityFramwork框架认识不足导致了这一BUG浪费了一天的时间,特此在这里记录。给自己一个警醒希望大家遇到相同问题能帮助到... 查看详情

记录一次bug修复-entityframworksavechanges()失效(代码片段)

...解决步骤六、总结一、前言这是笔者在参与一个小型项目开发时所遇到的一个BUG,因为项目经验不足对EntityFramwork框架认识不足导致了这一BUG浪费了一天的时间,特此在这里记录。给自己一个警醒希望大家遇到相同问题能帮助到... 查看详情

遇到的[]bug(代码片段)

...ndingtonullpointeroftype‘structvalue_type‘Lastexecutedinput:[]第一回遇到这样的错误,上网搜了资料,发现是因为输入的测试样例为[]时,返回错误的问题。其实自己也没搞懂为什么会有这样的错误233333,但是模糊地做了一下调整之后,竟... 查看详情

移动端bug兼容(代码片段)

总结一下目前的移动端开发遇到的问题。1、IOS与安卓input默认样式去除。移动端总有一个默认的圆角或别的。input[type=button],input[type=text],input[type=password]-webkit-appearance:none;outline:none;border-radius:none;2、IOS后退无刷新使用onpageshow主动... 查看详情

北大青鸟java培训:软件开发人员解决bug的方法?

参考技术A每个软件开发人员都会遇到bug,那bug是什么呢?当软件开发人员能够测试标准后发掘的问题成为bug。那么解决bug的方法有哪些呢?电脑培训http://www.kmbdqn.cn/建议首先软件开发人员需要掌握怎样快速定位,之后修改程序就可... 查看详情

记一次文件读写遇到的bug(代码片段)

代码一:#include<sys/types.h>#include<sys/stat.h>#include<fcntl.h>#include<iostream>#include<unistd.h>usingnamespacestd;intmain() char*buf=newchar[8]; //can't 查看详情

记一次文件读写遇到的bug(代码片段)

代码一:#include<sys/types.h>#include<sys/stat.h>#include<fcntl.h>#include<iostream>#include<unistd.h>usingnamespacestd;intmain() char*buf=newchar[8]; //can't 查看详情