hashset保证元素唯一性靠hashcode()定位桶、equals()精确判定:hashcode不同直接视为不同元素,相同则用equals()判断是否重复;二者必须成对重写且逻辑一致,否则导致重复添加或查找失败。

Java 中集合框架(尤其是 HashSet 和 HashMap)保证元素唯一性,本质是靠 Object 类的 hashCode() 和 equals() 方法协同工作。不是靠遍历比对,而是通过哈希定位 + 精确判定两步完成。
hashCode() 决定“去哪个桶”
向 HashSet 添加元素时,第一件事就是调用该对象的 hashCode() 方法。这个整数值被用来计算它在底层哈希表中的存储索引(即“桶”的位置)。如果两个对象 hashCode() 不同,它们绝不会被放进同一个桶,也就根本不会触发相等判断——哪怕内容一模一样,也会被当成不同元素存进去。
- String、Integer 等 JDK 类已正确重写这两个方法,所以直接用没问题
- 自定义类默认继承 Object 的
hashCode()(基于内存地址),每次 new 都不同 → 同样内容的对象会被视为多个 - 哈希值必须稳定:同一对象多次调用必须返回相同结果(不能依赖随机数、时间戳等)
equals() 做最终“是否真相同”的裁决
当新元素和桶里已有元素的 hashCode() 相同时(哈希冲突),HashSet 就会遍历桶内所有对象,逐个调用 equals() 方法比较。只有当 equals() 返回 true,才认定为重复,拒绝插入。
- 必须满足五项契约:自反、对称、传递、一致、对 null 安全
- 典型写法:先判
this == obj,再判obj == null || getClass() != obj.getClass(),最后用Objects.equals()比字段 - 漏掉类型检查或 null 判断,会导致
contains()、remove()报 NPE 或 ClassCastException
两个方法必须成对重写且逻辑一致
这是最容易出错的地方。JDK 明确要求:如果两个对象 equals() 返回 true,它们的 hashCode() 必须相同。违反这个约定,哈希结构就失效了。
- 只重写
equals()不重写hashCode()→ 相等对象散落在不同桶,Set 误加重复项 - 只重写
hashCode()不重写equals()→ 还是用默认引用比较,逻辑相等的对象仍不被识别 - hashCode 计算漏字段,或用了可变字段(如 later 修改的 name)→ 对象插入后“丢失”,再也找不回来
- 推荐统一用
Objects.hash(name, age)生成 hashCode,用Objects.equals(a, b)比较字段
替代方案:用 record 或 Lombok 省心避坑
手写容易遗漏边界或不一致。现代开发更推荐自动化方案:
-
record(Java 14+):适合纯数据类,自动实现不可变的
equals/hashCode/toString,语义清晰又安全 -
Lombok @EqualsAndHashCode:加注解即可,支持
exclude或onlyExplicitlyIncluded精确控制字段 - IDE 自动生成:IntelliJ / Eclipse 的 Generate 功能靠谱,但要手动核对字段是否都选对(尤其避开 id、timestamp 等非业务字段)
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











