不重写equals()和hashCode()会导致逻辑相等的key被HashMap视为不同key,引发重复插入、查不到值、remove失败;必须同时重写二者,且参与计算的字段须不可变,推荐用Objects.equals()和Objects.hash()。

Map的key不重写equals()和hashCode()会发生什么
直接后果是:两个逻辑上“应该相等”的key,在HashMap中会被当成完全不同的key,导致重复插入、查不到值、remove失败——不是bug,是设计使然。
因为HashMap定位key靠两步:hashCode()决定桶位置 → equals()在桶内逐个比对。如果只重写equals()不重写hashCode(),那两个对象可能被散列到不同桶里,equals()根本没机会被调用;如果两个都不重写,就退化成地址比较,哪怕字段一模一样,map.get(new Person("张三", 25))也永远返回null。
什么时候必须同时重写这两个方法
只要你的自定义类要作为Map的key,或放进Set(如HashSet、LinkedHashSet),就必须重写。String、Integer能当key用得好,是因为它们早已重写了这两个方法。
- 场景1:用
Person对象作key缓存用户权限:Map> permissionCache - 场景2:用
OrderKey(含orderNo+tenantId)作key聚合订单统计 - 场景3:把自定义对象丢进
ConcurrentHashMap做线程安全缓存
怎么写才不出错:契约和实操要点
核心就一条:只要equals()返回true,hashCode()就必须返回相同整数;反之不成立(哈希冲突允许)。
立即学习“Java免费学习笔记(深入)”;
推荐做法:
- 用
Objects.equals()和Objects.hash(),省去null判空和类型检查 - 参与
hashCode()和equals()的字段,必须是不可变的(或至少在作为key期间不变),否则插入后改了字段,再get()就找不到了 - 避免用随机数、时间戳、数据库主键(未赋值时为0)、或
toString()这类不稳定值参与计算
public class Person {
private final String name; // final更安全
private final int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Person person = (Person) o;
return age == person.age && Objects.equals(name, person.name);
}
@Override
public int hashCode() {
return Objects.hash(name, age);
}
}
常见踩坑现场
这些错误在代码审查或线上问题回溯中高频出现:
- 只重写
equals(),忘了hashCode()→ key看似一样,但map.containsKey(key)返回false - 重写了
hashCode()但用了可变字段(比如status),插入后改状态,再get()就失效 - 字段类型是
ArrayList或自定义对象,却没确保其自身也重写了equals()/hashCode()→ 外层Objects.equals()会递归调用,结果不可控 - 在IDE自动生成时勾选了所有字段,但其实业务上只有
id才算“相等”,多加字段导致本该合并的key被拆开
最隐蔽的问题是:它不报错,只是“查不到”“删不掉”“反复put”——这种逻辑错误比空指针更难定位。关键在于:只要key是自定义类,别信默认实现,重写是必选项,不是优化项。










