HashSet底层基于HashMap实现,元素作为key存储,value统一为PRESENT;唯一性依赖equals()和hashCode()协同,二者须同时重写;支持单个null;遍历顺序不保证。

HashSet底层用HashMap存储,key存元素、value固定用PRESENT
HashSet本身不直接管理元素去重,它内部持有一个HashMap实例,所有添加的元素都作为HashMap的key插入。而HashMap的key天然不允许重复——这正是HashSet实现唯一性的根本机制。它的value统一使用一个静态的Object对象PRESENT(仅占位,无业务意义)。
所以当你调用set.add("a"),实际执行的是map.put("a", PRESENT);第二次再add相同元素时,put会返回旧value(即PRESENT),但key没变,因此size不变,也不抛异常。
元素唯一性依赖equals()和hashCode()协同工作
仅仅重写hashCode()或equals()中的一个,会导致HashSet行为异常。必须同时满足:
- 两个逻辑上相等的对象(
a.equals(b) == true),必须返回相同的hashCode() - 如果
hashCode()不同,equals()一定不能返回true(这是HashMap桶定位的前提) - 若只重写
hashCode()而忽略equals(),可能把不同对象散列到同一桶,但因equals()返回false,仍会被视为不同元素 - 若只重写
equals()而hashCode()沿用Object默认实现(基于内存地址),那即使内容相同,哈希值也不同,它们会被分配到不同桶中,HashSet永远无法识别重复
添加null元素是合法的,且只能存在一个
HashSet允许存一个null值,因为HashMap支持key为null。其内部对null做了特殊处理:在put时,若key为null,直接放入table[0]位置,并用单独的逻辑判断是否已存在(避免调用null.hashCode())。
立即学习“Java免费学习笔记(深入)”;
这意味着:
-
set.add(null)第一次返回true,第二次返回false -
null和任何非null对象都不可能equals(),所以不会与其他元素冲突 - 不要在自定义类的
hashCode()里盲目判空后返回0——除非你同时确保equals()对null参数返回false,否则可能破坏契约
迭代顺序不保证,别依赖for-each输出顺序
HashSet不维护插入顺序,也不按大小或哈希值排序。它的遍历顺序取决于当前HashMap底层数组的结构、扩容时机、以及元素哈希值在桶中的分布——这些都可能随JDK版本、初始容量、负载因子甚至运行时环境变化。
如果你需要顺序保障:
- 用
LinkedHashSet(保持插入顺序) - 用
TreeSet(按自然序或定制比较器排序) - 临时排序可转成
List再Collections.sort(),但注意这已脱离HashSet语义
最常被忽略的一点:哪怕两次运行完全相同的代码,在不同JVM参数或不同JDK小版本下,HashSet的迭代顺序也可能不同——这不是bug,是设计使然。










