必须同时重写equals()和hashCode(),因HashSet基于HashMap实现,先用hashCode()定位桶再用equals()比较;若只重写equals(),相等对象可能因默认hashCode()不同被分到不同桶,导致重复添加。

自定义对象要能正确存入 HashSet,必须重写 equals() 和 hashCode() 方法——只重写 equals() 是不够的,且二者必须保持一致。
为什么必须同时重写 equals 和 hashCode
HashSet 内部基于 HashMap 实现,添加元素时先用 hashCode() 计算哈希值定位桶(bucket),再用 equals() 在该桶内比较是否已存在相同元素。如果只重写 equals() 而不重写 hashCode(),两个逻辑上相等的对象可能因默认 hashCode()(基于内存地址)不同,被分配到不同桶中,导致 HashSet 误判为“不重复”,从而重复添加。
重写 equals 的关键原则
遵循 Object.equals() 的通用约定:
-
自反性:对非 null 对象,
x.equals(x)必须返回true -
对称性:若
x.equals(y)为true,则y.equals(x)也必须为true -
传递性:若
x.equals(y)且y.equals(z)为true,则x.equals(z)也应为true - 一致性:多次调用结果不变(除非参与比较的字段被修改)
-
对 null 的处理:任何非 null 对象调用
x.equals(null)必须返回false
典型实现模板(以 Person 类为例)
假设 Person 用 name 和 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 中使用的字段严格一致
}
注意:Objects.equals() 可安全处理 null;Objects.hash() 自动生成哈希值,字段顺序和内容需与 equals() 完全对应。
常见错误与验证建议
- 只重写
equals()忘记hashCode()→ 测试时用相同字段构造两个对象,放入HashSet后检查大小是否为 1 -
equals()中用了字段 A、B,但hashCode()只基于字段 A → 导致哈希不一致,同样引发重复 - 使用可变字段(如普通 setter 修改的属性)参与 equals/hashCode → 对象加入
HashSet后若字段变更,可能导致无法查找或删除(违反集合契约) - 推荐在 IDE 中用 “Generate” 功能自动重写,或使用 Lombok 的
@EqualsAndHashCode注解(明确指定include字段)

















