retainAll会直接修改原集合而非返回新集合,调用后原数据丢失;需先复制再操作,注意不可变集合抛异常、null处理、equals/hashCode重写、性能差异及替代方案。

retainAll 会修改原集合,别当成纯函数用
retainAll 不是返回交集的新集合,而是**直接修改调用方集合**,只保留它和参数集合都有的元素。如果原集合是 ArrayList 或 HashSet,调用后内容就变了,原数据丢失。
常见错误是这样写:
List<string> a = new ArrayList(Arrays.asList("x", "y", "z"));
List<string> b = Arrays.asList("y", "z", "w");
a.retainAll(b); // ✅ 得到 ["y", "z"],但 a 已被改写
// 后续还想用原来的 a?不行了</string></string>
正确做法是先复制一份:
- 用
new ArrayList(original)或new HashSet(original)创建副本再调用retainAll - 如果原集合不可变(如
Collections.unmodifiableList),retainAll会直接抛UnsupportedOperationException - 注意
null元素:两个集合都含null,retainAll才会保留它;若只有一个含null,则不会保留
retainAll 的底层逻辑依赖 equals 和 hashCode
交集判断不是靠引用,而是靠 equals。这意味着:
- 自定义对象必须重写
equals(以及hashCode,尤其当用HashSet时) - 用
String、Integer等标准类型没问题;但用new String("a")和"a"比较,equals仍为true,所以能正常交集 - 如果参数集合是
TreeSet,且元素没实现Comparable或没传Comparator,运行时可能抛ClassCastException
示例:
AI 舌诊面诊问诊医疗系统 + 诊疗商城系统源码是一套结合 AI 辅助诊断与商品转化的健康类程序,基于浩创云、ThinkPHP(PHP)开发,支持微信服务号与 H5 端,适合做中医舌诊面诊问答、健康建议推送以及配套诊疗商品的运营。
List<person> list1 = ...; // Person 忘重写 equals → retainAll 结果为空或错乱</person>
性能差异大:ArrayList vs HashSet 调用 retainAll 效果不同
时间复杂度取决于「谁调用 retainAll」和「参数集合类型」:
-
ArrayList.retainAll(hashSet):O(n),因为HashSet.contains()平均 O(1) -
ArrayList.retainAll(anotherArrayList):O(n×m),因为每次都要遍历另一个ArrayList查找 -
HashSet.retainAll(arrayList):O(m),内部会把arrayList转成HashSet再算(JDK 8+ 优化过)
所以想快,优先让大集合做调用方,并确保参数是 HashSet 或 LinkedHashSet。
替代方案:不改原集合时用 Stream 或 Guava
如果只是要交集结果、不碰原数据,retainAll 就不是最顺手的工具:
- JDK 8+ 推荐:
list1.stream().filter(list2::contains).collect(Collectors.toList())—— 但注意list2::contains在ArrayList上仍是 O(n),建议先转Set - Guava 更简洁:
Sets.intersection(set1, set2)返回不可变视图,不改原集合,也不分配新集合(懒计算) - 自己封装工具方法时,别忘了判空:任一集合为
null,retainAll会抛NullPointerException
真正容易被忽略的是:很多人以为 retainAll 是“安全获取交集”的快捷方式,其实它本质是个破坏性操作,是否复制、是否校验、是否预转 Set,全得自己兜底。










