线上系统出现“查不到数据”等问题,多因equals和hashCode契约被破坏;需检查集合异常行为、参与比较的字段及重写实现是否满足相等对象hashCode相同、equals与hashCode用字段一致、null安全三原则。

线上系统出现“查不到数据”“去不了重”“Set 里重复添加”这类问题,十有八九不是逻辑写错了,而是 equals 和 hashCode 的契约被悄悄破坏了。排查这类 Bug 不靠猜,而要按线索顺藤摸瓜——重点看集合行为异常的现场、对象参与比较的字段、以及重写实现是否自洽。
从现象反推:哪些行为说明 equals/hashCode 出了问题
先确认是不是它在作祟:
- HashMap get 返回 null,但 key 看起来一模一样:说明 hashCode 定位错了桶,根本没走到 equals 比较那步;
- HashSet.add() 连续两次成功,内容相同的对象存了两份:hashCode 不同 → 分到不同桶 → equals 根本没被调用;
- ConcurrentHashMap computeIfAbsent 反复执行 lambda:key 被认为不相等,每次都被当新 key 处理;
- Stream.distinct() 去重失效:底层依赖的是 hashCode + equals,二者不一致时去重逻辑就崩了。
检查重写实现是否满足契约三原则
打开你出问题的实体类,逐条核对:
分析 Java Spring Boot 后端 Controller 代码,自动生成 Markdown 格式的接口文档。当用户说“生成 Java 接口文档”、“生成 API 文档”、“从 Spring 代码生成文档”时触发。
- 相等对象必须有相同 hashCode:挑两个业务上应视为相等的实例(比如 id 和 name 都相同),手动调用它们的 hashCode(),看返回值是否一致;
- equals 用了哪些字段,hashCode 就必须用完全相同的字段:常见错误是 equals 比了 id + name,hashCode 却只算了 id;
- 字段值为 null 时,equals 和 hashCode 是否都安全:比如直接调用 field.equals(other.field) 会 NPE,应改用 Objects.equals(field, other.field);hashCode 同理,要用 Objects.hash(id, name, email) 而非 id.hashCode() ^ name.hashCode()。
警惕那些“看起来一样、实际不等”的字段类型
有些字段表面可比,实则暗藏陷阱:
立即学习“Java免费学习笔记(深入)”;
- Date / LocalDateTime:毫秒精度不同、时区不同、或一个是 java.util.Date 一个是 java.time.Instant,equals 就是 false;
- BigDecimal:new BigDecimal("100") 和 new BigDecimal("100.00") 数值相等,但 equals 返回 false(因 scale 不同);建议统一用 stripTrailingZeros() 或转成字符串比较;
- 数组 / List:Objects.hash(arr) 对数组只算引用哈希,内容相同也返回不同值;必须用 Arrays.hashCode(arr) 或 Arrays.deepHashCode(arr);List 则没问题,因 ArrayList.equals 已按内容比较;
- 自定义对象字段:如果 User 包含一个 Address 字段,而 Address 类没重写 equals/hashCode,那整个 User 的相等性就不可靠。
用工具快速验证,别靠肉眼 debug
线上不敢加断点?试试这些轻量手段:
- 加日志打桩:在 equals 和 hashCode 方法入口加 log,输出关键字段值和返回结果,对比两个“应该相等”的对象调用过程;
-
JVM 参数辅助:启动时加 -XX:+PrintGCDetails 不相关,但可加 -Dsun.misc.URLClassPath.debug=true 无用;更实用的是用 Arthas 的 watch 命令实时观测方法返回值:
watch com.example.User hashCode returnObj -n 5; - 单元测试兜底:写一个最小复现用例,构造两个业务语义相同但内存地址不同的对象,断言 map.get(key) != null 且 set.size() == 1。

















