removeIf() 比循环删除更安全,因其是原子操作、避免 ConcurrentModificationException 和索引偏移漏删;但非线程安全,不支持 null Predicate,且在 LinkedList 和 CopyOnWriteArrayList 中性能差异显著。

removeIf 为什么比循环删除更安全
直接用 for 遍历并调用 list.remove() 容易触发 ConcurrentModificationException,或漏删相邻元素——因为索引会随删除动态偏移。而 removeIf() 是原子操作,底层由 ArrayList 或 LinkedList 自行处理迭代与结构变更,不暴露迭代器状态。
- 适用于所有实现了
Collection接口的类(ArrayList、LinkedList、CopyOnWriteArrayList) -
removeIf()返回boolean:只要删掉至少一个元素就返回true,可用于判断是否发生变更 - 对
CopyOnWriteArrayList,removeIf()会复制整个数组,大数据量时注意内存开销
Predicate 写法决定性能和可读性
谓词不是越复杂越好;重点是让 JVM 能高效匹配,同时避免副作用。比如在 Lambda 中调用远程接口、修改外部变量,会导致行为不可预测。
- 推荐写法:
list.removeIf(item -> item.getId() == null || item.isExpired()) - 避免写法:
list.removeIf(item -> { log.info("checking: " + item); return item.isExpired(); })(日志有副作用,且每次匹配都执行) - 如果条件来自外部变量,记得用
final或 effectively final:比如int threshold = 100; list.removeIf(item -> item.getValue() > threshold) - 对字符串等对象,优先用
Objects.equals(a, b)而非a.equals(b),防止NullPointerException
removeIf 在不同 List 实现上的表现差异
removeIf() 行为取决于具体实现类,不是“统一快”——尤其要注意 LinkedList 和 ArrayList 的底层逻辑完全不同。
-
ArrayList:先遍历标记要删的索引,再批量移动后续元素,时间复杂度接近O(n) -
LinkedList:需逐个节点检查并断链,实际可能比手动遍历还慢,特别是匹配率高时 -
CopyOnWriteArrayList:每次调用都新建数组,原数组不变,适合读多写少场景;但removeIf()是写操作,慎用于高频调用 - 若已知要删的元素下标集,对
ArrayList直接用Collections.reverseOrder()+ 索引删除反而更快
容易被忽略的空值与并发陷阱
很多人以为 removeIf() 天然线程安全——其实只在单线程下成立。一旦涉及并发读写,结果就不可控。
立即学习“Java免费学习笔记(深入)”;
-
removeIf(null)会抛NullPointerException,必须确保 Predicate 不为null - 若
List本身是unmodifiableList()包装的,removeIf()会立即抛UnsupportedOperationException - 多个线程同时调用同一个
ArrayList.removeIf()?没加锁就是数据竞争,可能漏删、重复删,甚至破坏数组结构 - 真要并发安全,要么用
CopyOnWriteArrayList(注意上面说的复制开销),要么外层加synchronized(list)
最常出问题的地方不在语法,而在默认假设“它能自动处理并发或空值”。写之前先看一眼 list 是谁造的、在哪用的、有没有其他线程碰它。


















