必须同时重写 equals 和 hashcode,因为哈希集合依赖二者协同工作:hashcode 定位桶,equals 确认相等;若不一致,会导致 hashset 重复添加、hashmap 查找不到、set.contains 返回 false 等问题。

只重写 equals 不重写 hashCode,或反过来,是 Java 开发中最隐蔽也最常出问题的误区之一。根本原因在于:哈希集合(如 HashSet、HashMap 的 key)依赖两个方法协同工作——hashCode 定位桶位置,equals 做最终确认。二者脱节,逻辑就断了。
为什么必须成对重写
Java 规范明确要求:如果两个对象通过 equals 判定为相等,它们的 hashCode 必须返回相同值。这不是性能建议,而是契约强制约束。
违反它会导致:
-
HashSet.add()可能重复添加“相同”对象(因为哈希码不同,被放进不同桶) -
HashMap.get(key)返回null,即使 key 确实存在(查找时去了错误的桶) -
Set.contains()返回false,明明对象已存在
典型错误写法及修正
常见错误是只改 equals,比如:
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Person p = (Person) o;
return age == p.age && Objects.equals(name, p.name);
}
// ❌ 没有重写 hashCode —— 危险!
正确做法是立即补上匹配的 hashCode:
@Override
public int hashCode() {
return Objects.hash(name, age); // 字段顺序和 equals 中一致
}
注意:参与 hashCode 计算的字段,必须且仅限于 equals 中实际比较的字段。多一个、少一个、用错字段(比如用了可变字段),都会破坏一致性。
继承场景下更要小心
若类可能被继承,用 instanceof 判断类型容易破坏对称性(父类实例 equals 子类实例为 true,但反过来不成立)。稳妥方式是用 getClass() == obj.getClass() 严格限定同类比较。
如果确实需要支持父子类间相等(如 JPA 实体),需明确定义规则并确保子类 hashCode 与父类兼容,通常更推荐将类声明为 final,一劳永逸。
借助工具和测试防踩坑
别手写——用 IDE 自动生成(IntelliJ 或 Eclipse 的 Generate → equals() and hashCode()),它默认使用 Objects.equals 和 Objects.hash,安全可靠。
更重要的是加单元测试:
- 验证自反性:
obj.equals(obj)→true - 验证对称性:
a.equals(b)与b.equals(a)结果一致 - 验证相等对象哈希码相同:
a.equals(b)为true时,a.hashCode() == b.hashCode() - 验证集合行为:
Set<t> s = new HashSet(); s.add(a); s.contains(b)</t>应返回true
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











