hashset 判断元素存在依赖 hashcode() 定位桶、equals() 精确比对;jdk 类已重写二者,自定义类须成对重写;treeset 等其他 set 底层机制不同,判断逻辑各异。

Java 中 HashSet 判断元素是否存在,核心靠 contains() 方法,底层依赖对象的 hashCode() 和 equals() 两个方法协同工作,不是简单遍历比较。
contains() 的实际执行流程
调用 set.contains(obj) 时,HashSet 并不会逐个遍历所有元素。它按以下两步快速定位:
- 先计算 obj.hashCode(),根据哈希值快速定位到内部哈希表(HashMap)的某个“桶”(bucket)位置
- 只在该桶中遍历节点,对每个节点的 key 调用 obj.equals(key) 进行精确比对
只要任一节点满足 equals 返回 true,就立即返回 true;桶中全部不匹配,则返回 false。
基本类型和包装类能直接用的原因
Integer、String、Boolean 等 JDK 自带类,已正确重写了 hashCode 和 equals,语义符合直觉:
-
new Integer(1).equals(new Integer(1))→ true - 它们的 hashCode 值也相同,保证哈希分布合理
- 所以
set.add(1); set.contains(1)必然返回 true
自定义对象必须重写两个方法
如果 Person 类没重写 hashCode 和 equals,哪怕两个对象字段完全一样,contains() 也可能返回 false —— 因为默认使用 Object 的实现,基于内存地址判断。
正确做法是两者成对重写,且逻辑一致:
- equals() 为 true 的对象,hashCode() 必须相等
- 推荐用
Objects.equals()和Objects.hash()简化编写,避免手算出错 - 字段选哪些,取决于你认为“什么才算同一个元素”(例如只看 id,或 name+age 组合)
其他 Set 实现的判断逻辑差异
虽然都用 contains(),但不同 Set 底层机制不同:
-
TreeSet:不依赖 hashCode,而是调用元素的
compareTo()或传入的 Comparator,通过红黑树二分查找,时间复杂度 O(log n) - LinkedHashSet:行为同 HashSet,但保留插入顺序;判断逻辑完全一致
- ConcurrentSkipListSet:线程安全,基于跳表排序查找,也不用 hashCode
所以选择哪种 Set,要结合是否需要排序、线程安全、null 支持等实际需求,不能只看 contains 是否存在。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











