集合遍历时删除元素

面试官问:"在遍历 ArrayList 时,怎么安全删除元素?"

候选人小邬答:"用 Iterator.remove()。"

面试官追问:"除了 Iterator,还有别的方法吗?"

小邬答不上来。

【面试官心理】 这道题考查的是候选人对 Java 集合遍历机制的掌握程度。能说出多种安全删除方式的候选人,说明有丰富的实战经验。

一、常见错误 🔴

List<String> list = new ArrayList<>(Arrays.asList("a", "b", "c"));

// ❌ 错误一:for-each 循环中删除
for (String s : list) {
    if ("b".equals(s)) {
        list.remove(s); // ❌ ConcurrentModificationException
    }
}

// ❌ 错误二:for 循环正序删除
for (int i = 0; i < list.size(); i++) {
    if ("b".equals(list.get(i))) {
        list.remove(i); // ❌ 可能跳过元素
        // 删除后 i++,i 指向下一个元素,可能跳过
    }
}

二、正确方法 🔴

2.1 Iterator.remove()

List<String> list = new ArrayList<>(Arrays.asList("a", "b", "c"));

Iterator<String> it = list.iterator();
while (it.hasNext()) {
    if ("b".equals(it.next())) {
        it.remove(); // ✅ 安全删除
    }
}
// list = ["a", "c"]

2.2 removeIf()(JDK 9+)

List<String> list = new ArrayList<>(Arrays.asList("a", "b", "c"));

// ✅ JDK 9+ 推荐
list.removeIf(s -> "b".equals(s));
// list = ["a", "c"]

2.3 倒序 for 循环

List<String> list = new ArrayList<>(Arrays.asList("a", "b", "c"));

// ✅ 倒序遍历,删除后索引不受影响
for (int i = list.size() - 1; i >= 0; i--) {
    if ("b".equals(list.get(i))) {
        list.remove(i);
    }
}

2.4 ConcurrentHashMap 的安全删除 🟡

ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();
map.put("a", 1);
map.put("b", 2);
map.put("c", 3);

// ✅ ConcurrentHashMap 可以用 Iterator.remove()
Iterator<Map.Entry<String, Integer>> it = map.entrySet().iterator();
while (it.hasNext()) {
    Map.Entry<String, Integer> entry = it.next();
    if ("b".equals(entry.getKey())) {
        it.remove(); // ✅ 安全
    }
}

三、性能对比 🟡

方法时间复杂度适用场景
Iterator.remove()O(n)通用
removeIf()O(n)JDK 9+,最简洁
倒序 forO(n)JDK 8-
Stream.filter()O(n)需要保留部分元素
// removeIf 底层实现
default boolean removeIf(Predicate<? super E> filter) {
    Objects.requireNonNull(filter);
    boolean removed = false;
    final Iterator<E> each = iterator();
    while (each.hasNext()) {
        if (filter.test(each.next())) {
            each.remove();
            removed = true;
        }
    }
    return removed;
}

四、Stream 方式 🟡

List<String> list = new ArrayList<>(Arrays.asList("a", "b", "c"));

// ✅ 保留符合条件的
List<String> filtered = list.stream()
    .filter(s -> !"b".equals(s))
    .collect(Collectors.toList());

// 注意:这是创建新列表,不是修改原列表
// 如果需要修改原列表:
list = list.stream()
    .filter(s -> !"b".equals(s))
    .collect(Collectors.toList());

五、生产选型 🟡

// JDK 9+:removeIf()(最简洁)
list.removeIf(s -> s.startsWith("test"));

// JDK 8-:Iterator.remove()
Iterator<String> it = list.iterator();
while (it.hasNext()) {
    if (it.next().startsWith("test")) {
        it.remove();
    }
}

// 多线程场景:ConcurrentHashMap.remove()

六、追问升级

面试官:"ConcurrentHashMap 在遍历时能安全删除吗?"

// ✅ 可以,但有限制
ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();
map.put("a", 1);
map.put("b", 2);

// 可以用 Iterator.remove() 安全删除
map.keySet().iterator().remove();

// 但不能:
// map.remove("b") // ❌ 在遍历时可能抛 ConcurrentModificationException
// 不应该在遍历时用 map.remove()
// 应该用 iterator.remove()