Java中HashMap用自定义对象作键时,必须同时重写hashCode()和equals()方法并保持逻辑一致,且键对象应尽量不可变,否则修改影响哈希值的字段会导致键“丢失”。

Java 中 HashMap 用自定义对象作键时,唯一性不是自动成立的,必须靠开发者主动保障:关键在于正确重写 hashCode() 和 equals() 方法,并确保对象在放入后不被修改影响哈希值。
必须同时重写 hashCode 和 equals
HashMap 判断两个键是否“相同”,分两步执行:
- 先调用
hashCode(),如果返回值不同,直接认定是不同键,不会继续比较 - 只有哈希值相等,才会调用
equals()进行内容比对;仅当equals()返回true时,才视为重复键,新值覆盖旧值
若只重写 equals(),不同对象可能散列到不同桶中,equals() 根本没机会执行;若只重写 hashCode(),所有对象挤进同一个桶,退化为链表遍历,性能差且仍无法语义去重。
两个方法逻辑必须一致
核心契约是:只要 equals() 返回 true,两个对象的 hashCode() 就必须返回相同整数。反过来说,哈希值相同不要求 equals() 一定为 true(允许哈希冲突),但参与 equals() 比较的字段,必须全部参与 hashCode() 计算。
立即学习“Java免费学习笔记(深入)”;
例如,若用 id 和 name 判断相等,就不能只用 id 算哈希;也不能在 hashCode() 中加入 createTime 这类未参与 equals() 的字段。
推荐用不可变对象作键
一旦对象作为键存入 HashMap,后续若修改了影响 hashCode() 的字段(比如改了 name),再执行 get() 或 remove() 时会重新计算哈希,可能跳到别的桶里找——原位置还留着它,却再也访问不到,造成“丢失”。
- 最佳实践是把键对象设计为不可变:字段用
final修饰,构造器一次性赋值,不提供 setter - 若必须可变,至少确保参与
hashCode()和equals()的字段,在放入 Map 后不再变更
IDE 自动生成 + 手动校验更可靠
IntelliJ 或 Eclipse 的 “Generate → equals() and hashCode()” 功能能快速生成标准代码,但要注意:
- 只勾选真正决定“逻辑相等”的字段(如业务主键、名称等)
- 排除时间戳、状态标记、缓存字段等可变或无关字段
- 检查生成的
hashCode()是否与equals()使用了完全相同的字段组合 - 若字段是引用类型(如另一个自定义对象),确认它自身也已正确定义了
hashCode()和equals()


















