知识点回顾
开发Java都知道,我们在使用POJO对象存储信息时,一定要重写 equals 方法和hashCode 方法
- 前者是为了解决当两个POJO对象执行equals时,对比具体数值信息,而不是直接对比对象本身。
- 后者是为了解决因为在使用散列数据结构时,比如哈希表,我们希望相等的对象具有相等的哈希码。
具体可以参考CSDN文章,讲的很好
场景模拟复原
给出一个具体的场景,我们需要实现一个物资分配的功能,每一次前端都会传递一个物资列表,需要我们进行持久化,并且需要判断物资的变化情况。
- 比如原物资分配了A、B,新物资分配了A、C、D,那么出了需要将物资修改为ACD,还需要判断本次变化减少了物资B,新增了物资C和D
因此,结合需求,我们就可以使用到equals方法,来判断两个物资是否发现变化。
代码简单模拟
物资对象如下:
@Data
public class Good extends BaseEntity {private String name;private BigDecimal amount;}
企业开发的实体对象一般会继承一个基类,简单模拟
public class BaseEntity {Long id;}
在实际开发场景中,经常会选择手动重写、IDEA快速生成、Lombok注解的方式来重写equals和hashcode方法,这些都是可行的。接下来的坑点排查和生成方式无关,以IDEA自动生成的方法为例。
@Override
public boolean equals(Object o) {if (this == o) return true;if (o == null || getClass() != o.getClass()) return false;if (!super.equals(o)) return false;Good good = (Good) o;return Objects.equals(name, good.name) && Objects.equals(amount, good.amount);
}@Override
public int hashCode() {return Objects.hash(super.hashCode(), name, amount);
}
坑点一,直接和查询得到的结果比对出错。
以下模拟物品db是从数据库中查询得到的物品,我们需要和dto进行对比。尝试复原模拟实际场景
Good db = new Good();
db.setId(100L);
db.setName("原油");
Good dto = new Good();
dto.setName("原油");System.out.println("两者是否相等:"+ db.equals(dto));
打印如下
Good(name=原油, amount=0)
Good(name=原油, amount=0)
两者是否相等:false
原因分析
虽然两个物品的信息虽然一模一样,但是由于从数据库查询得到的物品db是带有主键id的信息,我们会发现物品的equals方法中有调用了父级的equals方法,但是最终结果不符合预期
解决方案
应该根据实际场景来判断是否需要调用父级的equals方法;比如该场景中,对比的是商品的名称和数量,id是不需要对比的
修改后的equals方法如下
@Override
public boolean equals(Object o) {if (this == o) return true;if (o == null || getClass() != o.getClass()) return false;Good good = (Good) o;return Objects.equals(name, good.name) && Objects.equals(amount, good.amount);
}
重新运行结果如下,符合场景
Good(name=原油, amount=0)
Good(name=原油, amount=0)
两者是否相等:true
坑点二、数值对比异常
很多时候我们使用bigdecimal存储数值时,进入数据库也会有对应的decimal数据类型。
尝试复原这个场景如下
Good db = new Good();
db.setAmount(new BigDecimal("100.00"));
db.setId(100L);
db.setName("原油");
Good dto = new Good();
dto.setAmount(new BigDecimal(100.00));
dto.setName("原油");
System.out.println("两者是否相等:"+ db.equals(dto));
Good(name=原油, amount=100)
Good(name=原油, amount=100.00)
两者是否相等:false
虽然我们存储的数值是一样的,但是判断结果为false,出现异常。
原因分析
深究代码即可发现,不管是IDEA自动生成的equals方法,还是lomok提供的注解生成的方法,对于bigdecimal类型的处理都是调用BigDecimal的equals方法进行处理.。
解决方案
要对比BigDecimal官方的API推荐是使用CompareTo()
方法。
方法进行以下重写即可:
public boolean equals(Object o) {if (this == o) return true;if (o == null || getClass() != o.getClass()) return false;Good good = (Good) o;boolean res = false;if (amount!=null && good.amount !=null){res = amount.compareTo(good.amount) == 0;}return Objects.equals(name, good.name) && res;
}
进一步简写
public boolean equals(Object o) {if (this == o) return true;if (o == null || getClass() != o.getClass()) return false;Good good = (Good) o;return Objects.equals(name, good.name) &&(amount == null && good.amount == null ||amount != null && good.amount != null && amount.compareTo(good.amount) == 0);
}
进行测试,结果如下,符合预期。
Good(name=原油, amount=100.00)
Good(name=原油, amount=100)
两者是否相等:true
经验总结
- 重写equals方法应该要根据实际的业务场景,选择需要的字段进行重写。特别是在使用lombok或者IDEA快速一键生成时,一定要根据业务场景进行选择使用。
- 遇到一些特殊的数据类型时,切忌无脑使用equals方法,应该使用规定的方法进行比较。