Java中equals方法比了个寂寞?原来这才是正确的重写姿势 去年在金融项目里处理一笔对账数据时我们突然发现系统漏掉了300多条记录——明明两个Transaction对象的金额和账户ID完全一致HashSet.contains()却始终返回false。排查到最后问题竟然出在一个看似简单的equals方法上。你是不是也以为重写equals就是比较几个字段今天咱们就聊聊那些年equals方法里藏过的坑。当equals遇上HashSet一个对账系统的惨案我们的对账模块需要比对两个数据源的交易记录逻辑很简单用HashSet存储基准数据然后遍历待核对数据调用contains()比对。在测试环境一切正常但生产环境跑完总有漏网之鱼。 通过日志最终锁定问题某些Transaction对象虽然业务字段值相同但hashCode()返回值却不一样——因为有人偷懒只重写了equals却忘了hashCode。这里有个硬规则如果两个对象equals返回true它们的hashCode必须相同。否则当对象作为键存入HashMap或HashSet时会存在哈希桶定位错误的风险。// 错误示范只重写equals Override public boolean equals(Object o) { if (this o) return true; if (o null || getClass() ! o.getClass()) return false; Transaction that (Transaction) o; return amount.equals(that.amount) accountId.equals(that.accountId); } // 正确姿势必须配套重写hashCode Override public int hashCode() { return Objects.hash(amount, accountId); // 使用相同字段 }为什么Lombok的EqualsAndHashCode也翻过车你可能会说我用Lombok自动生成不就完了别急去年另一个坑正出在这里。某次上线后监控突然显示某个核心服务CPU飙到90%堆栈显示卡在HashMap.get()——因为自动生成的hashCode()包含了全部字段而其中一个字段是BLOB类型的大文本关键点hashCode()必须满足一致性对象不变时返回值不变但不要求所有字段参与运算。对于重型字段应该手动排除EqualsAndHashCode(onlyExplicitlyIncluded true) public class Order { EqualsAndHashCode.Include private Long id; // 只使用轻量级字段 private byte[] pdfContent; // 排除大字段 }实测对比包含10KB文本字段的类调用hashCode()耗时从1200ns降至60nsJMH基准测试预热后结果。谁杀死了你的equals性能假设你给用户权限系统写了这样的equals// 性能杀手无谓的字符串比对 Override public boolean equals(Object o) { //...省略判空 User user (User) o; return username.equals(user.username) permissions.stream() .sorted() .collect(joining(,)) .equals(user.permissions.stream() .sorted() .collect(joining(,))); }问题出在每次比较都要对流进行排序和拼接。如果权限列表有20项单次equals耗时直接突破1ms实测数据。对于高频调用的场景这种写法就是自杀。优化原则优先比较大概率不等的字段避免深层嵌套结构的完全遍历return username.equals(user.username) permissions.size() user.permissions.size() permissions.containsAll(user.permissions); // 前提是Set实现避坑清单equals重写的军规hashCode必须和equals同步重写违反这条直接导致HashMap/HashSet行为异常避免可变字段参与计算如果参与equals的字段会被修改对象作为Map键时将失踪数组字段用Arrays.equals比较直接调用array.equals()比较的是引用浮点数字段用Double.compare处理NaN和±0.0等特殊情况子类equals必须满足里氏替换原则子类新增字段会导致对称性被破坏终极答案什么时候才该重写equals经过这些年踩坑我的判断标准只有两条需要对象逻辑相等而非引用相等时比如值对象确定该对象会作为集合元素或Map键使用时否则直接使用Object的默认实现反而是最安全的选择。你在项目里遇到过哪些equals的奇葩坑欢迎分享你的血泪史。