在前面一篇文章 equals和hashCode的暗操作 中,我们指出,只要重写 equals,就必须重写 hashCode方法,也举了例子,在HashSet中去重失效的问题。
现在,我们就来看看真实业务上遇到的equals和hashCode引起的bug,实际上也是个去重的问题,引起了集合数据丢失。
主要涉及到,两个知识,
在一次公用平台的问题反馈中,用户服务的 queryUserListByRoleNames 接口,丢失数据,从数据库查询发现

本接口排除了七条数据,不知道这七条为啥被排除?
查看代码,通过Debug发现,查询的确是查到了16条数据,只是最后一步,加了个distinct去重,就丢失了7条数据,但是这7条数据并不跟其他数据冲突,也没有重复,为什么会被过滤掉了呢?

找到 List userDtoList = new ArrayList<>(); 代码中对应的实体类UserDto。

我们这个类UserDto,继承了基础类BaseUser,且引入注解:@EqualsAndHashCode(callSuper = false) ,这是什么意思?
不扯太多废话了,意思就是,EqualsAndHashCode注解是来自于lombok的。
其中 @EqualsAndHashCode注解:在JavaBean或类JavaBean中使用时,会自动重写实体的equals方法和hashCode方法。
需要注意的点是:属性 callSuper。
属性 callSuper的作用:在重写hashcode()和equals()方法时决定是否调用父类的属性。默认就是false。
比如callSuper = false:表示会在对象比较时不考虑父类中的成员,仅仅比较子类中的属性就判断是否相同。
说白了,就是当我们用于对象属性比较时,只比较子类的属性,也就是讲: 如果两个对象子类属性一致,就算父类属性不一致,在比较时也会出现两个对象是相同的,返回的true这样的结论。”
那么问题就来了,class UserDto extends BaseUser ,父类BaseUser中,包含了关键字段,但却是被忽略的。

而UserDto 中,都是一些附加字段,比如sex、phone等等。
在案例中,对象是:
UserDto(sex=0, userId=10070, account="lt3",userName="李婷3")
UserDto(sex=1, userId=10071, account="lt4",userName="李婷4")
UserDto(sex=0, userId=10072, account="lt5",userName="李婷5")
distinct 方法是根据 hashcode 和 equal 方法去判定是否是同一个元素的,底层也是HashSet方式来实现去重。所以,在使用distinct 方法前,一定要重写 对象类的 hashcode 和 equals 方法。
具体可以看源码,stream接口,