retainAll() 直接修改原集合,若原集合不可变(如Arrays.asList返回的)会抛异常;用Stream需将list2转HashSet避免O(m×n)性能问题;生产中应据是否可改原集合及数据规模选方案。

retainAll() 为什么有时候改了原集合却没拿到交集
因为 retainAll() 是「就地修改」操作,它会直接清空调用方集合中不在参数集合里的元素,而不是返回新集合。如果你还拿着原来的引用去用,可能已经为空了。
- 必须确保调用方集合是可变的(比如
new ArrayList()),Arrays.asList()返回的列表不支持修改,调用会抛UnsupportedOperationException - 如果需要保留原集合,得先
new ArrayList(list1)拷贝一份再调retainAll(list2) - 底层遍历逻辑依赖
equals()和hashCode(),自定义对象务必重写这两个方法,否则交集永远为空
Stream filter 实现交集时性能掉坑的三个常见点
用 stream().filter(list2::contains).collect(Collectors.toList()) 看似简洁,但 contains() 在 ArrayList 上是 O(n) 查找,整体变成 O(m×n),数据量一过千就明显卡顿。
- 把
list2转成HashSet再传入:Set<t> set2 = new HashSet(list2); ... filter(set2::contains)</t> - 注意
null值:如果list2含null,HashSet可存一个null,但filter中若元素为null,set2::contains仍能正确判断;不过若用TreeSet就会直接抛NullPointerException - 流式写法默认不保序,但
filter+collect保持原始顺序,这点和retainAll()一致,不用额外处理
哪种方式更适合生产环境的交集计算
没有银弹,取决于你手上的集合类型和是否允许修改原集合。
- 已知两个都是小集合(retainAll(),代码短、无额外对象开销
- 要保持原集合不可变,或集合来自不可变容器(如
Collections.unmodifiableList())→ 必须用Stream+HashSet预热 - 频繁计算交集(比如在循环里),建议把
list2的HashSet提到外层缓存,避免重复构造 -
retainAll()对LinkedList性能极差(每次contains都要遍历),这种场景必须换Stream方式
自定义对象交集失效时最先该查什么
90% 的情况不是逻辑写错,而是 equals() 和 hashCode() 没配对实现,或者只重写了其中一个。
立即学习“Java免费学习笔记(深入)”;
- 检查是否漏了
@Override注解,IDE 有时不会报错但实际没覆盖父类方法 - 用
Objects.equals(a, b)和Objects.hash(...)来写,避免手动判null出错 - 如果用了 Lombok,确认
@EqualsAndHashCode的include或exclude字段是否符合业务语义(比如 ID 和 name 都要参与比较,但 createTime 不用) - 调试时临时加一句
System.out.println(list1.get(0).hashCode() == list2.get(0).hashCode()),快速验证哈希值一致性
retainAll() 的副作用和 Stream 中 contains() 的隐式复杂度,最容易被忽略。










