跳转到主内容
趣航编程网 - 趣学编程,启航技术之路!

如何理解元素存入HashSet时hashCode与equals的调用顺序

HashSet去重严格按“先hashCode()后equals()”两步执行:桶空则只调hashCode(),桶非空才依次调equals();仅重写equals()不重写hashCode()会导致去重失效,因默认哈希值基于内存地址,相同内容对象被分至不同桶,equals()永不触发。 存入HashSet时,先调用
hashCode()
,再可能调用
equals()
HashSet判断一个新元素是否已存在,严格按两步走:先算
hashCode()
,再决定要不要调用
equals()
。不是“两个都调”,也不是“随机选一个”,而是有明确的短路逻辑。 具体流程是:
hashCode()
返回的值决定了该对象落在哈希表哪个“桶”(bucket)里;如果那个桶里当前为空,直接插入,
equals()
根本不会被触发;只有桶里已有至少一个对象,才会拿新对象和桶内每个对象依次调用
equals()
比对。 桶为空 → 只调
hashCode()
,不调
equals()
桶非空 → 先调
hashCode()
定位桶,再对桶内每个对象调
equals()
hashCode()
不同 → 绝对不调
equals()
,哪怕两个对象内容完全一样 为什么只重写
equals()
却无法去重 因为
hashCode()
没重写时,默认继承自
Object
,返回的是内存地址的哈希值。两个内容相同的自定义对象,只要不是同一个实例(
==
为
false
),
hashCode()
就大概率不同 —— 这导致它们被分到不同桶里,
equals()
压根没机会运行。 常见错误现象:
new Person("Alice", 30)
加了两次进
HashSet
,结果集合大小是2,而不是1。打印调试会发现
equals()
一次都没被调用过。 重写
equals()
但不重写
hashCode()
→ 去重失效,且无任何报错或警告 IDE(如IntelliJ)在重写
equals()
时通常会提示“also override
hashCode()
”,这不是建议,是契约 用
Objects.hash(...)
生成
hashCode()
最安全,它自动处理
null
和浮点数比较等边界
hashCode()
相同但
equals()
返回
false
是正常现象 哈希冲突不可避免。比如两个
Person
对象,一个叫"Bob"年龄25,另一个叫"Tom"年龄25,若
hashCode()
只基于
age
计算,它们哈希值就相同,但
equals()
会因
name
不同而返回
false
。这时HashSet会把它们都存进同一个桶,用链表或红黑树组织。 这种设计是权衡:用空间换时间。哈希码负责快速过滤掉99%的不等对象;
equals()
兜底处理剩下那1%的冲突。 哈希值相同 ≠ 对象相等,只是“需要进一步确认”的信号 过度追求
hashCode()
分散(比如加入随机因子)反而破坏一致性,导致
contains()
查不到自己刚
add()
的对象 一旦
hashCode()
依赖可变字段(如
name
后续被
setName()
修改),对象存进HashSet后就再也找不到了 自定义类放进HashSet前必须检查的三件事 不是“写了
equals
和
hashCode
就行”,得看它们是否真正协同工作。最容易忽略的是语义一致性 —— 什么情况下你认为两个对象“应该算重复”?这个业务逻辑必须同时体现在两个方法里。 确认
equals()
中用的字段,和
hashCode()
里
Objects.hash(...)
传的字段完全一致 验证交换顺序是否影响结果:比如
Comparison(se1, se2)
和
Comparison(se2, se1)
应视为相同,则
hashCode()
不能简单写成
Objects.hash(se1, se2)
,得用对称写法(如排序后哈希) 字段含
double
类型时,避免直接用
==
比对,在
equals()
里用
Double.compare()
,在
hashCode()
里用
Double.hashCode()
而非
(int) value
实际开发中,最常踩的坑不是不会写,而是改了业务逻辑后忘了同步更新其中一个方法。只要
hashCode()
和
equals()
对同一组字段做同构运算,且不引入可变状态,HashSet的去重行为就是可预测的。

相关文章