必须同时重写 equals 和 hashCode,且二者逻辑严格一致:equals 基于哪些字段比较,hashCode 就必须用相同字段计算;需满足自反性、对称性、传递性、一致性、非空性;推荐用 Objects.equals() 和 Objects.hash() 保障 null 安全与继承兼容。

重写 equals 和 hashCode 不是“写了就行”,而是必须严格对齐业务语义与 Java 契约。很多 Bug 表面看是集合查不到、去不了重,根子都在这两个方法没写对。
只重写 equals 却漏掉 hashCode
这是最典型也最危险的陷阱。比如你精心写了 equals 比较 id 字段,但没动 hashCode,那默认仍按内存地址算——两个 id 相同的 User 对象哈希值不同,HashSet 会把它们塞进不同桶里,contains() 就永远返回 false;HashMap 的 get() 也会查不到已存的键。
- 只要类可能作为
HashMap的 key 或放进HashSet/Hashtable,就必须同时重写两者 - IDE(如 IntelliJ)自动生成时默认勾选“同时生成”,别手动取消
- 用
Objects.equals()和Objects.hash()能天然规避空指针和字段遗漏
equals 实现违反五大契约
Java 规范要求 equals 必须满足:自反性、对称性、传递性、一致性、非空性。手写时稍不注意就破戒。
Java Maven 项目审查工作流的通用输入处理。当 Java Maven 项目以 ZIP压缩包或 GitLab 仓库 URL 形式提供且需要...时使用。
- 忘记判
null或类型不匹配(getClass() != obj.getClass()),会导致NullPointerException或对称性失效 - 用
instanceof替代getClass()看似方便,但在有继承关系时会破坏对称性(父类实例equals子类实例为true,反过来却为false) - 在
equals中读取了可变字段(比如某个计算属性每次调用结果不同),就违反了一致性
hashCode 计算逻辑与 equals 不一致
hashCode 必须基于 equals 中用于判断相等的**同一组字段**计算。如果 equals 看 id 和 name,但 hashCode 只用了 id,那 id 相同但 name 不同的对象就会哈希冲突——这不是错误,但若 equals 返回 false,它们本不该被当成同一个键。
立即学习“Java免费学习笔记(深入)”;
- 推荐统一用
Objects.hash(id, name),它自动处理null,且字段顺序和equals中的比较顺序保持一致 - 避免在
hashCode中调用耗时方法或访问外部状态(如数据库、当前时间) - 如果对象字段后续可能被修改,且该对象已放入哈希集合中,务必确保这些字段在生命周期内不可变(即设计为不可变类),否则哈希值变化会导致对象“消失”在集合里
忽略 null 安全与继承场景
字段为 null 时直接调用 .equals() 或 .hashCode() 会抛异常;而子类扩展字段后,若父类 equals 没预留扩展点,容易出错。
- 一律用
Objects.equals(a, b)替代a.equals(b),它能安全处理任一参数为null - 子类重写
equals时,先调用super.equals(obj)再检查子类特有字段;hashCode同理,用Objects.hash(super.hashCode(), childField) - 如果类明确不允许继承,加
final修饰,省去类型检查的纠结

















