equals与hashCode#
一句话答案#
重写 equals 必须重写 hashCode,相等对象 hashCode 必须相同,HashMap 先比较 hashCode 再比较 equals。
核心要点
原因:equals() 和 hashCode() 之间有契约(Contract):
如果两个对象
equals()返回true,则它们的hashCode()必须相同。
如果只重写 equals() 不重写 hashCode():
class Person {
String name;
@Override
public boolean equals(Object o) {
return this.name.equals(((Person) o).name);
}
// 没有重写 hashCode(),使用默认的 Object.hashCode()(基于内存地址)
}
Person p1 = new Person("Alice");
Person p2 = new Person("Alice");
p1.equals(p2); // true(重写了 equals)
p1.hashCode() == p2.hashCode(); // false!(内存地址不同)
// 放入 HashMap 后出问题:
Map<Person, String> map = new HashMap<>();
map.put(p1, "value");
map.get(p2); // 返回 null!因为 p2 的 hashCode 不同,定位到错误的桶javaHashMap 的查找流程:
- 计算 key 的
hashCode()→ 定位桶位置 - 在桶中用
equals()逐个比较
如果 hashCode() 不一致,第一步就定位错误,永远找不到对应的 key。
正确做法:
@Override
public int hashCode() {
return Objects.hash(name); // 基于 name 计算,保证 equals 相同时 hashCode 也相同
}java面试回答(2分钟版)
equals和hashCode之间存在一个核心契约:如果两个对象equals返回true,它们的hashCode必须相同。这个契约的根本原因在于HashMap的查找机制——先用hashCode定位到数组中的桶位置,再在桶内用equals逐个比较找到目标元素。如果只重写equals不重写hashCode,两个逻辑相等的对象可能hashCode不同,导致它们被分配到不同的桶中,用一个对象put进去后用另一个逻辑相等的对象get时根本定位不到正确的桶,返回null。Object默认的hashCode基于内存地址计算,所以不重写的话两个new出来的对象即使内容一样hashCode也不同。正确做法是使用Objects.hash()方法基于参与equals比较的字段来计算hashCode,保证equals相等时hashCode一定相同。反过来hashCode相同equals不一定相同,这就是哈希碰撞。还有一个容易被忽略的问题:如果一个对象作为HashMap的key放入后,其参与hashCode计算的字段被修改了,会导致再也找不到这个entry,造成内存泄漏,所以HashMap的key最好用不可变对象。
追问与易错
追问方向:
- “为什么重写 equals 必须重写 hashCode?”→ HashMap 查找时先用 hashCode 定位桶位置再用 equals 逐个比较,若 equals 相等但 hashCode 不同会定位到错误的桶,get 返回 null
- “hashCode 相同 equals 一定相同吗?”→ 不一定,这就是哈希碰撞。hashCode 相同只说明在同一个桶中,还需要 equals 进一步判断是否是同一个 key
- “HashMap 中 key 的 hashCode 变了会怎样?”→ 已存入的 entry 无法被找到(因为 hash 值变了定位到不同的桶),造成内存泄漏,所以 HashMap 的 key 最好用不可变对象
易错点:
- ❌ 反过来说”equals 相等 hashCode 可以不同”——这会违反契约,HashMap 行为异常
- ❌ 忘记 null 值处理——equals 实现中要处理 null 和类型检查