必须同时重写 hashCode() 和 equals() 方法;否则内容相同的对象可能被放入不同哈希桶,导致去重失效。应使用参与逻辑相等判断的字段计算 hashCode,用 Objects.hash() 和 Objects.equals() 确保安全。

关键在于让两个“逻辑上相同”的对象,返回相同的哈希值,并且彼此 equals 为 true。HashSet 不是靠内存地址认人,而是靠你写的这两个方法来判断“是不是同一个”。
为什么必须同时重写?
HashSet 底层用 HashMap 实现,把元素当 key 存。插入时分两步:
- 先调 hashCode() 算出哈希值,决定放在哪个桶(数组索引)里
- 再在那个桶里遍历已有元素,用 equals() 逐个比对——只有哈希值相同 且 equals 返回 true,才算重复
如果只重写 equals(),不重写 hashCode(),那两个内容一样的对象可能被分到不同桶里,equals 根本没机会被调用,结果就是“明明该去重却没去重”。
怎么写才安全?
核心原则:属性相同时,hashCode 必须相同;hashCode 相同,equals 才值得比较。
- 用所有参与逻辑相等判断的字段参与 hashCode 计算,比如 name 和 age 都影响“是不是同一个人”,就都得进 hash
-
equals 要做空值检查、类型检查、字段逐一对比,不能只写
this.name == other.name && this.age == other.age(会 NPE 或 ClassCastException) - 推荐用
Objects.hash(name, age)生成 hashCode,用Objects.equals(a, b)做字段比较,简洁又防空
验证是否写对了
写完别急着跑业务,快速验证两件事:
- 创建两个属性完全相同的对象,打印它们的 hashCode() —— 必须相等
- 用这两个对象调 equals() —— 必须返回 true
- 把它们 add 进 HashSet,最后 size 应该是 1,不是 2
常见踩坑点
这些细节容易忽略,但一错就失效:
- 用了可变字段(比如一个 List 属性)参与 hash 或 equals,后续修改 List 后,对象在 HashSet 里可能再也找不到了
- 重写了 equals 但忘了 @Override,实际还是调的 Object 的默认实现
- hashCode 里用了 new Date()、Math.random() 这类每次都不一样的值,导致同一对象多次 hash 结果不同
- 把数据库主键 id 当唯一依据,但新对象还没保存,id 是 null 或 0,多个新对象就全撞一起了

















