HashSet去重依赖hashCode()和equals()协同:先用hashCode()定位桶,再用equals()判断是否已存在;二者必须同时重写且逻辑一致,否则自定义对象无法正确去重。

HashSet 去重的底层原理是什么
HashSet 去重依赖 hashCode() 和 equals() 两个方法协同工作:添加元素时,先用 hashCode() 定位桶位置,再用 equals() 判断是否已存在相同元素。如果这两个方法没按约定重写,自定义对象就无法正确去重。
常见错误现象:new HashSet(list) 对自定义类列表去重后大小没变——大概率是忘了重写 hashCode() 和 equals()。
- 必须同时重写
hashCode()和equals(),只重一个会导致行为不一致 - 重写时要确保逻辑一致:若
a.equals(b)返回true,则a.hashCode() == b.hashCode()必须为true - 使用 IDE(如 IntelliJ)自动生成最稳妥,避免手写逻辑漏洞
如何对 List 或基本类型包装类快速去重
字符串、Integer、Long 等 JDK 自带类已正确实现 hashCode() 和 equals(),可直接用 HashSet 构造器去重,无需额外处理。
Listoriginal = Arrays.asList("a", "b", "a", "c"); Set uniqueSet = new HashSet<>(original); List uniqueList = new ArrayList<>(uniqueSet); // 若需保持 List 类型
注意:HashSet 不保证顺序,原列表中重复元素的“首次出现”无法保留;若需有序去重,请改用 LinkedHashSet:
立即学习“Java免费学习笔记(深入)”;
SetorderedUnique = new LinkedHashSet<>(original);
Stream.distinct() 和 new HashSet() 哪个更适合去重
Stream.distinct() 内部正是基于 LinkedHashSet 实现的,语义清晰但有轻微开销;new HashSet() 更轻量,适合简单场景。
- 需要链式操作(如过滤后再去重):用
stream().filter(...).distinct().collect(...) - 仅去重且不关心顺序:
new HashSet(list)最直接 - 需保持插入顺序 + 去重:优先选
new LinkedHashSet(list),比stream().distinct()少一次遍历 -
大数据量时,两者性能差异不大,但
HashSet构造器无 lambda 调用开销,略快
去重后想转回 List 但又怕顺序错乱
直接用 new ArrayList(set) 会丢失原始顺序,因为 HashSet 无序;而 LinkedHashSet 虽保持插入顺序,但它的“插入顺序”是去重后的唯一元素顺序,并非原列表中首次出现的位置顺序。
若严格要求“保留原列表中每个元素第一次出现的位置”,不能只靠 Set,得手动遍历:
Listoriginal = Arrays.asList("c", "a", "b", "a", "c"); Set seen = new HashSet<>(); List uniqueInOrder = original.stream() .filter(e -> seen.add(e)) .collect(Collectors.toList());
这里关键点是 seen.add(e) 的返回值:首次添加返回 true,重复时返回 false,从而实现“按原序取首次出现项”。这个细节容易被忽略,导致误用 distinct() 后以为顺序安全。










