核心是确保对象的hashCode()存入HashSet后不变化:推荐设计不可变类,将参与hashCode()和equals()的字段声明为final;若必须可变,则修改前先remove再add;避免用易变字段参与哈希计算,并用工具静态检查隐患。

核心是不让对象的 hashCode() 在存入后发生变化——HashSet 不会跟踪、也不会更新对象的位置,它只认插入那一刻算出的哈希值。
设计不可变类(最推荐)
把参与 hashCode() 和 equals() 的字段声明为 final,构造时一次性赋值,之后禁止修改:
- 例如:用
String name和int id构造Person,不提供setName()或setId()方法 - 所有 getter 返回原始值或不可变副本(如
return new ArrayList(list)) - IDE 自动生成
hashCode()和equals()时,只勾选final字段
必须可变时,修改前先移除再添加
如果业务确实需要运行时改字段(比如状态更新),不能跳过同步步骤:
- 先调
set.remove(obj),让对象从哈希表中彻底退出 - 再修改字段,如
obj.setStatus("done") - 最后调
set.add(obj),重新计算哈希并插入正确位置 - 注意:这个过程不是原子的,多线程下需额外加锁或使用线程安全集合
避免用易变字段参与哈希计算
检查 hashCode() 实现里是否包含了可能被修改的字段:
立即学习“Java免费学习笔记(深入)”;
- 比如用数据库自增 ID 做哈希基础,但该 ID 在对象加载后又被重置为 0,就会出问题
- 优先选择天然不变的标识:UUID、主键(只读)、创建时间戳等
- 若字段语义上可变(如“最后更新时间”),就别放进
hashCode()计算逻辑
用工具辅助发现隐患
静态检查能提前暴露风险:
- 启用 IDE 警告:如 IntelliJ 的 “Mutable field used in hashCode()” 检查
- 在单元测试中模拟修改后查找:存入对象 → 修改字段 → 调
contains()验证是否返回false - 对关键实体类加注解(如
@Immutable),配合 Checker Framework 进行编译期校验


















