引言

今天中午正在带着耳机遨游在代码的世界里,被运营在群里@了,气冲冲的反问我最近有删生产的用户数据的吗?我肯定客气的回答道没有呀?生产的数据我怎么能随随便便可以删除,这可是公司的红线,再说了我也没有数据库的删除权限啊,不过查询权限还是有的。赶紧登上堡垒机,然后去生产数据库查一下数据,查了一下数据是还在的,吓死了,数据还在问题就不大了,无非就是应用程序出问题了,赶紧打开代码查看下,为什么会少了一条用户数据,看了下代码貌似没啥问题就是比较简单的一个逻辑,直接从DB通过分页查询数据给到前端,然后前端负责展示,没有啥复杂的逻辑。心想肯定是前端的问题,肯定是他少展示了数据,立马把问题也甩给了他,让他帮忙配合一起看看是否是前端的问题,然后自己也仔细看看代码,不到一分钟前端说他展示的数据没有问题,都是后端给到的,没有漏掉展示的。那就是后端的bug了罗。肉眼望去觉得可能出问题的就是分页导致的数据丢了。不过这个分页插件是全公司都在用,应该不至于出问题把,找不到问题只能让测试帮忙在测试环境试试,看看是否可以复现。

测试环境复现

仔细看了一眼,居然有个去重的方法,去重逻辑也比较简单就是把list通过转为set去下重,看下来应该就是这个去重方法有问题了
大致写了单元测试模仿了下生产的数据,大致逻辑如下:

public static void main(String[] args) {
        Set<UserDTO> userSet = new HashSet<>();
        UserDTO userDTO = new UserDTO();
        userDTO.setId(1);
        userDTO.setUserName("java金融");

        UserDTO userDTO1 = new UserDTO();
        userDTO1.setId(2);
        userDTO1.setUserName("java金融");
        userSet.add(userDTO);
        userSet.add(userDTO1);
        System.out.println(userSet.size());
        System.out.println(userDTO1.equals(userDTO));
    }

    @Data
    static class UserDTO extends BaseDTO {
        private String userName;
    }
    @Data
    static class BaseDTO {
        private Integer id;
    }

我们可以输出结果set集合的长度是1,user1user2 是相等的,明明两个userID是不一样的,为何会相等,我们知道set可以去重
是因为Set的操作,都是通过操作map来实现的,setadd其实就是调用mapput方法,mapput方法我相信大家应该都去看过其源码,这里就不详细再说了,大概流程就是通过key通过hash算法定位到数组的下标,先判断keyhash是否相等,如果相等再去判断key的value相等,如果都相等就会覆盖原来的值。我们上面这个例子就是对象的hashvalue都相等导致,但是我们的两个对象user1user2 应该是不等的,因为ID不等,那为什么会相等列?我们仔细看下上面的代码,我们使用了lombok里面@Data注解,我们可以看看这个注解帮我们生成了哪些方法
在这里插入图片描述
在这里插入图片描述
通过上面的对比我们可以看出@Data注解帮我们生成了 注在类上,提供类的get、set、equals、hashCode、canEqual、toString方法,这个注解确实比较方便。上面那个bug 就是因为它生成的equals方法有问题,我们可以把上述代码编译下,然后把class 里面生成的equals方法拷贝出来看看
在这里插入图片描述

通过上述生成的代码我们可以看出equals方法只比较了userName这个字段,也就是当前类的字段,并没有去比较父类的字段,这就是导致两个对象相等的原因,我们既然找到问题了,那解决问题就比较简单。

解决问题

  • 手动重写equalshashCode方法,这种方法肯定是不推荐的,我们既然用了lombok就是为了解放我们的双手,是代码变得更加简洁。
  • 在比较的类上加上@EqualsAndHashCode(callSuper = true) callSuper = true 会包含父类的equalshashCode方法
    我们可以对比下加上@EqualsAndHashCode(callSuper = true)和没有加上这个注解生成的equals方法的代码差异。
    在这里插入图片描述
    差异点还是很明显的,加入了@EqualsAndHashCode(callSuper = true) 会去调用父类的equals方法比较,所以这个注解也能够解决这个问题。
  • 这样加上注解确实可以解决问题,但是每个类上面都要加上这个注解,这也是个体力活。我们可以再找找其他的方案,例如有没有比如配置文件设置下什么的,然后就能全局生效了。最终通过查询资料发现我们我们写一个lombok.config的配置文件放在我们项目的根目录下面,内容写上lombok.equalsAndHashCode.callSuper = call效果等同于@EqualsAndHashCode(callSuper = true),这样的话我们就不需要为每个类都去加上这个注释了,相当于在这个项目下面只要用到了@Data注解的类都会为其加上@EqualsAndHashCode(callSuper = true)通过配置文件的方式就可以全局生效。

总结

  • 我们再来回顾下上面的问题,归根结底还是由于对象的equals方法使用不当引起的,所以我们在如果在判断自定义对象业务判断相等的时候需要去重写下hashCodeequals方法,重写的时候我们可以通过idea来生成,生成后最好还是去看一眼,看看生成的是否符合我们的业务要求。

  • 我们在工作中操作一些常见的容器类比如Set、Map等来实现一些我们自己的业务,我们还是有必要去看看它们的源码的,就比如我们通过Set来进行去重,如果我们是使用的自定义对象的话,如果没有重写hashCodeequals方法的话,去重就会去不成功,我们只有了解了它,才能真正的去用好它。在关于hashCodeequals 阿里巴巴开发手册也有明确的说到
    在这里插入图片描述

  • lombok 用起来还是挺爽的,但是还是有一些细节需要稍微注意下。使用前可以大概的去看看它的官网提供的内容,不然出现莫名其妙的问题你都不知道如何下手。这个就有点类似于我们使用SpringBoot一样,用起来非常爽,但是如果遇到莫名其妙的bug解决起来就比较头疼。

  • 最后我们再来回顾几道关于hashCodeequals的比较常见的面试题?其实如果我们只要真正看过HashMap的源码的话,这下面几个面试题还是非常简单的。
    什么情况下需要我们去重写 方法?
    如果只重写equals方法不重写HashCode可以吗?
    equals ,== 和hashcode()的区别?

结束

  • 由于自己才疏学浅,难免会有纰漏,假如你发现了错误的地方,还望留言给我指出来,我会对其加以修正。
  • 如果你觉得文章还不错,你的转发、分享、赞赏、点赞、留言就是对我最大的鼓励。
  • 感谢您的阅读,十分欢迎并感谢您的关注。