HashSet自动去重的核心是底层用HashMap存储,元素作key、固定Object作value;依赖hashCode()和equals()判断重复,二者需合理重写;允许一个null;非线程安全,遍历时修改会抛ConcurrentModificationException。

Java HashSet 配合泛型安全存储数据,核心不是“加了泛型就自动安全”,而是泛型约束 + 正确重写 hashCode() 和 equals() 二者缺一不可。泛型只管编译期类型检查,去重逻辑完全由这两个方法决定。
泛型声明要明确具体类型
避免使用原始类型(new HashSet())或泛型擦除后失控的 Set。应直接指定业务语义明确的类型:
-
Set
names = new HashSet(); —— 字符串去重,天然安全 -
Set
userIds = new HashSet(); —— 自定义 ID 类,需保证 equals/hashCode 一致 - 不推荐:Set set = new HashSet(); 或 Set> unknowns = ... —— 失去类型约束,add 时可能混入不兼容对象
自定义类必须重写 hashCode 和 equals
泛型声明 Set
- 只重 equals() 不重 hashCode() → 同值对象可能散列到不同桶,add() 或 contains() 返回 false,去重失效
- 两个逻辑相等对象返回不同 hashCode() → 被视为不同元素,导致重复入库
- 添加进 Set 后修改影响 hashCode() 的字段(如 name、id)→ 原桶中找不到该对象,remove() 或 contains() 失败
- 建议:用 IDE 自动生成,或手动使用 Objects.hash(id, name) 和 Objects.equals(this.id, other.id) && Objects.equals(this.name, other.name)
泛型配合 null 和线程安全的注意事项
泛型本身不限制 null,但业务上需主动控制:
- HashSet 允许且仅允许一个 null。若用 Set
存用户昵称,应提前校验 if (nick == null) 再 add,避免意外插入 null 导致后续 NPE - 泛型不提供线程安全。多线程共用同一个 Set
实例时,必须加锁,或改用 ConcurrentHashMap.newKeySet()(JDK 8+ 推荐) - 避免在 Spring Bean 中用 static final Set
缓存共享状态,否则跨请求污染风险高
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











