HashSet去重自定义对象需同时重写hashCode()和equals(),因底层用hashCode定位桶、equals确认相等;二者必须一致,且应基于业务唯一字段(如id)实现,避免可变字段和null异常。

HashSet 去重自定义实体类对象,关键不是“往里加”,而是**让对象自己说清楚:什么情况下算重复**。这靠重写 hashCode() 和 equals() 两个方法,缺一不可。
为什么必须同时重写 hashCode 和 equals?
HashSet 底层是 HashMap(JDK 8 起),它把对象的 hashCode() 当作数组下标快速定位“桶”,再用 equals() 在桶内逐个比对确认是否真正相等。如果只重写 equals(),相同逻辑的对象可能散落在不同桶里,根本不会被比较;如果只重写 hashCode(),哈希值一样但 equals() 返回 false,也会被当作不同对象存进去。两者必须保持一致:相等的对象(equals() == true)必须有相同的哈希值。
怎么写才正确?(以 User 类为例)
假设你希望两个 User 对象在 id 相同时视为重复:
- 用 IDE 自动生成(如 IntelliJ 的
Alt + Insert → equals() and hashCode()),选中决定“相等性”的字段(比如id) - 手动写时,
hashCode()推荐用Objects.hash(id)(JDK 7+),简洁且处理 null 安全 -
equals()要先判 null、再判 class、再逐字段比较(用Objects.equals(a, b)处理 null)
示例代码片段:
Java项目代码review工具。分析Git变更+完整调用链路上下文,推断业务需求,进行多维度评分和分类汇总,生成完整PRD文档。包含细粒度Java代码审查清单(Null安全、异常处理、Streams、并发、equals/hashCode、资源管理、API设计、性能、MyBatis/ORM、事务边界、SQL/DD...
立即学习“Java免费学习笔记(深入)”;
public class User {
private Long id;
private String name;
// 构造、getter 省略...
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
User user = (User) o;
return Objects.equals(id, user.id);
}
@Override
public int hashCode() {
return Objects.hash(id);
}
}
常见踩坑点
-
字段选错:比如用
name去重,但名字可能重复;业务上真正唯一标识对象的字段才是关键 -
修改了参与 equals/hashCode 的字段:对象加入 HashSet 后再改
id,会导致无法被contains()或remove()正确识别(因为哈希位置变了) - 用了可变字段但没同步更新哈希:HashSet 不会自动感知字段变化,所以最好用不可变字段(如 final id)做判断依据
-
忽略 null 安全:手写
equals时直接调id.equals(...)可能 NPE,要用Objects.equals()
验证是否生效的小技巧
写个简单测试:
- 创建两个
User对象,id相同、其他字段不同 - 放进
HashSet,打印 size —— 应该是 1,不是 2 - 调用
set.contains(new User(1L, "xxx")),应返回 true
不复杂但容易忽略。核心就一条:谁决定“一样”,就拿谁去算 hash 和比 equals。

















