首页 人工智能

Java List.remove 踩坑记:避免索引越界与 ConcurrentModificationException

分类:人工智能
字数: (2997)
阅读: (3727)
内容摘要:Java List.remove 踩坑记:避免索引越界与 ConcurrentModificationException,

在日常开发中,我们经常会用到 List.remove() 方法来删除列表中的元素。看似简单的操作,如果不注意,很容易踩坑,导致程序出现 Bug。本文就来深入剖析 List.remove() 的常见问题,并给出相应的解决方案。

问题场景重现:循环删除元素

最常见的错误用法就是在循环中删除元素。考虑以下代码:

import java.util.ArrayList;
import java.util.List;

public class ListRemoveExample {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        list.add("A");
        list.add("B");
        list.add("C");
        list.add("B");
        list.add("D");

        // 错误示例:循环删除所有 "B" 元素
        for (int i = 0; i < list.size(); i++) {
            if (list.get(i).equals("B")) {
                list.remove(i); // 删除元素后索引会发生变化
            }
        }

        System.out.println(list); // 输出结果可能不是你想要的
    }
}

这段代码的目的是删除列表中所有值为 "B" 的元素。但是,由于 List.remove(i) 在删除元素后,后面的元素会向前移动,导致循环索引 i 可能跳过某些元素,最终无法正确删除所有 "B" 元素。这个场景在涉及到数据库数据同步、缓存更新等业务逻辑时,可能会导致严重的数据不一致问题,如果前端直接依赖后端返回的错误数据,会造成更严重的后果。

Java List.remove 踩坑记:避免索引越界与 ConcurrentModificationException

底层原理深度剖析:索引变化与并发修改

ArrayListremove(int index) 方法的实现大致如下:

public E remove(int index) {
    rangeCheck(index); // 检查索引是否越界

    E oldValue = elementData(index);

    int numMoved = size - index - 1;
    if (numMoved > 0)
        System.arraycopy(elementData, index+1, elementData, index,
                         numMoved); // 移动元素
    elementData[--size] = null; // clear to let GC do its work

    return oldValue;
}

关键在于 System.arraycopy() 这行代码,它会将被删除元素后面的所有元素向前移动一位,这导致原本在 i+1 位置的元素移动到了 i 位置,而下一次循环 i 会自增 1,导致跳过了一个元素。 另外,如果在多线程环境下对 List 进行操作,更容易出现 ConcurrentModificationException,即使使用 Collections.synchronizedList 包裹的 List 也不能完全避免。

Java List.remove 踩坑记:避免索引越界与 ConcurrentModificationException

正确的 List.remove 使用姿势:多种解决方案

以下是几种正确的删除 List 元素的方法:

  1. 倒序循环删除:从列表的末尾开始循环,这样删除元素不会影响未遍历的元素索引。

    Java List.remove 踩坑记:避免索引越界与 ConcurrentModificationException
    for (int i = list.size() - 1; i >= 0; i--) {
        if (list.get(i).equals("B")) {
            list.remove(i);
        }
    }
    
  2. 使用 Iterator 删除:使用 Iteratorremove() 方法可以安全地删除元素,避免 ConcurrentModificationException

    import java.util.Iterator;
    
    Iterator<String> iterator = list.iterator();
    while (iterator.hasNext()) {
        if (iterator.next().equals("B")) {
            iterator.remove(); // 使用 iterator.remove()
        }
    }
    
  3. 使用 removeIf 方法:Java 8 引入的 removeIf 方法可以更简洁地删除满足条件的元素。

    Java List.remove 踩坑记:避免索引越界与 ConcurrentModificationException
    list.removeIf(element -> element.equals("B"));
    
  4. 创建新 List:将不需要删除的元素添加到新的 List 中,最后用新 List 替换原 List。 这种方式在数据量比较大的时候,会占用比较多的内存。

    List<String> newList = new ArrayList<>();
    for (String element : list) {
        if (!element.equals("B")) {
            newList.add(element);
        }
    }
    list.clear(); // 清空原 List
    list.addAll(newList); // 将新 List 的元素添加到原 List 中
    

实战避坑经验总结:选择合适的方案

  • 循环删除元素时,切记不能使用正序循环,推荐使用倒序循环或 Iterator
  • 如果使用 Java 8 及以上版本,优先考虑使用 removeIf 方法,代码更简洁易懂。
  • 多线程环境下操作 List,务必使用线程安全的集合类,如 CopyOnWriteArrayList 或使用锁进行同步。
  • 大数据量 List 的处理,需要注意内存占用,避免频繁创建新对象,可以考虑分批处理,或者使用流式处理框架,比如 Apache Flink 或者 Apache Spark 。在使用 Nginx 作为反向代理服务器时,也要注意后端服务的并发连接数,避免因为大量的并发请求导致服务崩溃。 也可以使用宝塔面板等工具来进行服务器的监控和管理。避免出现 OOM。
  • 涉及到复杂的业务逻辑时,要进行充分的单元测试和集成测试,确保代码的正确性。

掌握了这些技巧,相信你就能在实际开发中避免 List.remove 带来的各种问题,写出更加健壮的代码。

Java List.remove 踩坑记:避免索引越界与 ConcurrentModificationException

转载请注明出处: 程序员小强

本文的链接地址: http://m.acea4.store/blog/578191.SHTML

本文最后 发布于2026-04-22 02:50:53,已经过了5天没有更新,若内容或图片 失效,请留言反馈

()
您可能对以下文章感兴趣
评论
  • 咸鱼翻身 1 天前
    Iterator 的 remove 方法可以避免 ConcurrentModificationException,这个很重要。
  • 非酋本酋 5 天前
    感谢分享,List.remove 确实需要注意,不然很容易埋雷!