最直接解法是用collectors.groupingby(function.identity(), collectors.counting())分组计数,再filter取value>1的key;需注意泛型擦除、null安全及避免漏掉entryset().stream()转换。

用 Collectors.groupingBy 统计频次再过滤
Java 8+ 最直接的解法不是遍历比对,而是借力流式 API 做分组统计。核心逻辑是:先按元素本身分组计数,再筛出出现次数大于 1 的键。
常见错误是只调用 groupingBy 却忘了接 counting(),导致返回 Map<t list>></t>,白白多占内存;或者漏掉 entrySet().stream() 这一层转换,无法继续过滤。
- 必须搭配
Collectors.counting(),否则得不到数字频次 - 结果是
Map<t long></t>,要取重复项得用entrySet().stream().filter(e -> e.getValue() > 1).map(Map.Entry::getKey).collect(Collectors.toList()) - 注意泛型擦除:若
List是原始类型或含null,groupingBy会抛NullPointerException
List<String> list = Arrays.asList("a", "b", "a", "c", "b", "b");
List<String> duplicates = list.stream()
.collect(Collectors.groupingBy(Function.identity(), Collectors.counting()))
.entrySet().stream()
.filter(e -> e.getValue() > 1)
.map(Map.Entry::getKey)
.collect(Collectors.toList()); // ["a", "b"]用 Set 辅助做单次遍历去重判断
如果只要知道哪些元素重复、不关心重复几次,且对性能敏感(尤其大数据量),用 HashSet 辅助是更轻量的选择:边遍历边记录已见元素,第二次遇到即为重复。
容易踩的坑是误用 add() 返回值逻辑——它返回 false 表示“已存在”,但新手常写成 if (set.add(e)) { /* 重复 */ },实际应是 if (!set.add(e))。
立即学习“Java免费学习笔记(深入)”;
-
Set.add()返回true表示新增成功,false表示已存在 - 重复元素可能被多次加入结果集,需额外用
LinkedHashSet去重并保序 - 该方法不支持
null元素(除非用ConcurrentHashMap.newKeySet()等替代)
Set<String> seen = new HashSet<>();
Set<String> duplicates = new LinkedHashSet<>();
for (String item : list) {
if (!seen.add(item)) {
duplicates.add(item);
}
}用 Collections.frequency 的陷阱与适用场景
这个工具方法看着简单,但实际只适合小列表或调试场景。它内部是遍历计数,对每个元素都全表扫描一次,时间复杂度是 O(n²),10 万条数据就明显卡顿。
典型误用是嵌套在循环里反复调用,比如先 for (String s : list) 再 if (Collections.frequency(list, s) > 1),等于把 O(n²) 执行了 n 次,变成 O(n³)。
- 仅建议用于长度
- 不要在循环体内调用,可先去重(
new HashSet(list)),再对外层集合遍历调用 - 返回值是
int,但传入null时行为取决于具体List实现(如ArrayList支持,Arrays.asList包装的数组不支持)
自定义对象重复判断必须重写 equals 和 hashCode
如果 List 装的是自定义类(如 User),所有上述方案都会失效——除非你明确告诉 JVM “两个对象什么时候算同一个”。默认的引用比较会让所有对象都不同。
最常被忽略的是只重写 equals 忘了 hashCode,导致 HashSet 或 groupingBy 分组错乱:相同对象被散列到不同桶,统计结果漏项或重复。
- 必须同时重写
equals和hashCode,且逻辑一致(比如都基于id字段) - 字段选型要稳定:避免用可变字段(如
name)做判等依据,否则对象修改后哈希值变化,集合行为不可预测 - IDE 生成的模板基本可用,但要注意是否包含
null安全检查(推荐用Objects.equals(a, b))
重复判定本质是“相等性定义”问题,不是算法问题。没重写 equals/hashCode 就跑分组统计,结果一定不对;用 frequency 处理大列表,线程会卡住;而依赖 Set 辅助时,add() 的布尔返回值方向搞反,就永远抓不到重复项。









