HashSet去重最快但需重写equals()和hashCode();自定义对象未重写会导致去重失效;需保序用LinkedHashSet;Stream.distinct()语义清晰但有封装开销;按字段去重用toMap()并指定合并策略。

用 HashSet 去重最常用,但要注意元素必须正确重写 equals() 和 hashCode()
直接把 List 转成 HashSet 是最快捷的去重方式,前提是元素类型能正确判断相等性。比如 String、Integer 这类 JDK 自带类型没问题;但自定义对象(如 User)若没重写 equals() 和 hashCode(),即使字段值一样也会被当作不同对象保留。
实操建议:
- 对
ArrayList:直接new HashSet(list)即可 - 对自定义类:检查是否已实现
equals()和hashCode(),IDE 通常可自动生成 - 注意:
HashSet不保证顺序,原列表顺序会丢失
需要保持插入顺序?用 LinkedHashSet
LinkedHashSet 是 HashSet 的子类,底层用链表维护插入顺序,去重效果一致,但遍历时按首次出现顺序返回。
示例:
立即学习“Java免费学习笔记(深入)”;
Listlist = Arrays.asList("a", "b", "a", "c"); Set set = new LinkedHashSet<>(list); // 结果:[a, b, c]
常见误用:
- 误以为
TreeSet能保持“原始顺序”——它按自然序或比较器排序,不是插入顺序 - 在多线程场景下直接用
LinkedHashSet:它不是线程安全的,需额外同步
Java 8+ 推荐用 Stream.distinct(),语义清晰且可链式操作
distinct() 内部就是基于 HashSet 实现的,但封装了逻辑,代码更易读,尤其适合配合过滤、映射等操作。
示例:
立即学习“Java免费学习笔记(深入)”;
Listunique = list.stream().distinct().collect(Collectors.toList());
注意点:
- 必须确保元素支持
equals()/hashCode(),否则无效 - 性能上略低于直接构造
HashSet(多了 Stream 封装开销),但对大多数业务场景无感 - 不能用于基本类型数组(如
int[]),需先转为Integer流
复杂对象去重:用 Stream.collect() 配合 toMap() 更灵活
当去重依据不是整个对象,而是某个字段(比如只按 user.getId() 去重),distinct() 就不够用了。这时可用 Collectors.toMap() 指定 key 和 value,并处理重复 key 的策略。
示例(按 id 去重,保留第一个出现的对象):
ListuniqueByid = list.stream() .collect(Collectors.toMap( User::getId, user -> user, (u1, u2) -> u1 // 冲突时保留第一个 )) .values() .stream() .collect(Collectors.toList());
关键细节:
-
(u1, u2) -> u1表示遇到重复 id 时保留先出现的那个;换成(u1, u2) -> u2就是保留后一个 - 如果字段可能为
null,User::getId作 key 会抛NullPointerException,需提前过滤或用Objects.toString(id)等兜底 - 这个方法本质是“去重 + 转 Map”,比写循环手动判重更函数式,但也更难调试
equals() 和 hashCode(),或者忽略 null 值、线程安全、顺序要求这些隐性条件。









