retainAll()用于保留两集合交集,修改原列表仅存共同元素;需用HashSet提升性能,重写equals/hashCode,线程不安全,替代方案有removeIf、倒序循环或Stream.filter。

ArrayList 的 retainAll() 方法用于“保留交集”,即只留下当前集合中也存在于指定集合里的元素,相当于批量“过滤保留”;它不是删除操作,但效果常被理解为“删掉不在目标集合中的元素”。
retainAll 的作用和本质
调用 list.retainAll(other) 后,list 会修改为仅包含同时存在于 other 中的元素(去重逻辑基于 equals())。这等价于:
- 遍历原 list,逐个判断是否在
other中存在(other.contains(item)) - 只保留存在的项,其余全部移除
- 原 list 被直接修改,返回
true表示发生了变化,false表示没变(交集等于原集合)
使用注意事项
这个方法看似简单,但容易踩坑:
-
other 集合最好用 HashSet:如果
other是 ArrayList 或 LinkedList,每次contains()都是 O(n) 查找,整体变成 O(m×n),性能差;换成HashSet后,contains()接近 O(1),整体接近 O(m) -
元素必须正确重写
equals()和hashCode():否则retainAll判断“是否相等”会出错,尤其是自定义对象 - 线程不安全:不能在多线程环境下直接调用,除非手动同步
-
不支持基本类型数组直接传入:比如
int[]不行,需转成Integer包装类列表或用 Stream 处理
替代方案:更可控的批量删除
如果想明确“删除某些元素”,而不是“保留某些元素”,可以用以下方式:
立即学习“Java免费学习笔记(深入)”;
-
用 removeIf()(推荐,JDK 8+):
list.removeIf(item -> !targetSet.contains(item));
语义清晰,且内部优化好,配合HashSet效率高 -
倒序 for 循环 + remove()(兼容老版本):
避免因删除导致索引偏移,适合小数据量 -
Stream.filter().collect()(不可变方式):
List<t> result = list.stream().filter(targetSet::contains).collect(Collectors.toList());</t>
不修改原 list,适合函数式风格或需要保留原始数据的场景
一个小例子
假设有学生列表,只想保留学号在白名单里的学生:
List<Student> allStudents = ...; // 原始列表
Set<Integer> whiteListIds = new HashSet<>(Arrays.asList(101, 102, 105));
// ✅ 推荐:先转 set,再 retainAll 或 removeIf
allStudents.retainAll(
allStudents.stream()
.filter(s -> whiteListIds.contains(s.getId()))
.collect(Collectors.toList())
); // ❌ 错!这样写效率低且逻辑绕
// ✅ 正确做法:构造一个临时的 ID → Student 映射 or 直接用 removeIf
allStudents.removeIf(s -> !whiteListIds.contains(s.getId()));


















