在 Java 中从 for 循环中移出元素的原理及解决方案

news/2024/7/7 20:43:08 标签: java, 循环, for循环, 数组下标越界

在 Java 中从 for 循环中移出元素的原理及解决方案

在 Java 编程中,从集合中移除元素是常见操作。然而,直接在 for 循环中删除或移出元素往往会导致 ConcurrentModificationException(数组下标越界)。本文将详细解释这种情况发生的原因以及如何正确、安全地移出元素。

原理解释

Java 的集合框架如 ArrayList、HashSet 等在进行遍历时,通过创建一个迭代器来跟踪当前遍历的位置。这个迭代器会维护一个称为“修改计数器”(modCount)的变量,用于记录集合的结构性修改(例如添加或删除元素)。

在遍历过程中,如果集合的 modCount 与迭代器的 modCount 不一致,迭代器会检测到这一情况,并抛出 ConcurrentModificationException。这是一种快速失败机制,旨在防止在遍历时修改集合,避免可能的数据不一致和其他潜在问题。

错误示例

直接在 for-each 循环中删除元素会导致 ConcurrentModificationException。以下示例展示了这种情况:

java">import java.util.ArrayList;
import java.util.List;

public class RemoveElement {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        list.add("one");
        list.add("two");
        list.add("three");

        // 直接在for循环中移出元素,会抛出ConcurrentModificationException
        for (String item : list) {
            if (item.equals("two")) {
                list.remove(item); // 抛出异常
            }
        }
    }
}

运行上述代码会抛出 ConcurrentModificationException,因为在迭代时修改了集合。

解决方案

为了解决这个问题,有几种常用的方法可以安全地在循环中移除元素:

1. 使用 Iterator 的 remove 方法

使用 Iterator 的 remove 方法是推荐的方式,它可以安全地在遍历时移出元素。

java">import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

public class RemoveElement {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        list.add("one");
        list.add("two");
        list.add("three");

        Iterator<String> iterator = list.iterator();
        while (iterator.hasNext()) {
            String item = iterator.next();
            if (item.equals("two")) {
                iterator.remove(); // 安全移除元素
            }
        }

        System.out.println(list); // 输出 [one, three]
    }
}

在上述代码中,Iterator 的 remove 方法确保在迭代过程中安全地移除元素。

2. 使用 ListIterator

对于 List 接口的实现,可以使用 ListIterator,它提供了 remove 方法,并且可以安全地在遍历时移出元素。

java">import java.util.ArrayList;
import java.util.List;
import java.util.ListIterator;

public class RemoveElement {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        list.add("one");
        list.add("two");
        list.add("three");

        ListIterator<String> iterator = list.listIterator();
        while (iterator.hasNext()) {
            String item = iterator.next();
            if (item.equals("two")) {
                iterator.remove(); // 安全移除元素
            }
        }

        System.out.println(list); // 输出 [one, three]
    }
}

3. 使用普通 for 循环(倒序遍历)

对于 ArrayList 这种随机访问列表,可以使用倒序遍历的普通 for 循环来避免问题。这种方式不会触发 ConcurrentModificationException,因为在倒序遍历时删除元素不会影响未遍历的元素。

java">import java.util.ArrayList;
import java.util.List;

public class RemoveElement {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        list.add("one");
        list.add("two");
        list.add("three");

        for (int i = list.size() - 1; i >= 0; i--) {
            if (list.get(i).equals("two")) {
                list.remove(i); // 安全移除元素
            }
        }

        System.out.println(list); // 输出 [one, three]
    }
}

4. 使用 Stream 的过滤操作

在 Java 8 及以上版本中,可以使用 Stream 的过滤操作来创建一个新的集合,不包含要移除的元素。

java">import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

public class RemoveElement {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        list.add("one");
        list.add("two");
        list.add("three");

        list = list.stream()
                   .filter(item -> !item.equals("two"))
                   .collect(Collectors.toList()); // 过滤元素并收集到新的列表

        System.out.println(list); // 输出 [one, three]
    }
}

总结

在 Java 中,直接在 for-each 循环中移除元素会导致 ConcurrentModificationException,因为集合的结构被修改而迭代器无法跟踪这些修改。解决方案包括:

  • 使用 Iterator 的 remove 方法。
  • 使用 ListIterator。
  • 使用倒序遍历的普通 for 循环
  • 使用 Java 8 的 Stream 过滤操作。

这些方法确保在迭代过程中,集合的结构修改能被正确处理,避免异常的发生。通过理解这些原理和解决方案,可以编写更健壮和高效的代码。


http://www.niftyadmin.cn/n/5535171.html

相关文章

QT 自绘树形控件

资源来自:https://gitee.com/qt-open-source-collection/NavListView/blob/master/navlistview.h 1、解决的问题:一处编译报错;空白区域绘制背景;点击页面崩溃 2、源码: #ifndef NAVLISTVIEW_H #define NAVLISTVIEW_H/*** 作者:feiyangqingyun(QQ:517216493) 2016-10-1…

隐私集合求交(PSI)原理深入浅出

隐私集合求交技术是多方安全计算领域的一个子问题&#xff0c;通常也被称为安全求交、隐私保护集合交集或者隐私交集技术等&#xff0c;其目的是允许持有各自数据集的双方或者多方&#xff0c;执行两方或者多方集合的交集计算&#xff0c;当PSI执行完成&#xff0c;一方或者两方…

CEPH client.admin key获取

通过初始化完毕后&#xff0c;admin节点会在/etc/ceph目标下生成对应的配置文件和对应的key文件&#xff0c;通过ceph orch host add 增加的默认是没有的 如果很不幸admin节点挂了&#xff0c;怎么在其它节点使用ceph -s 命令呢 启蒙方法(比较实用) key可以通过ceph auth expor…

esp32-nvs使用

_____ 使用步骤 1, 初始化 nvs 分区 2, 打开 namespaec 3, 读取或写入键值 如果是 string 或数组类型,先读长度 -- 获取单个数据的值 1, 初始化分区 // Initialize NVSesp_err_t err = nvs_flash_init();if (err == ESP_ERR_NVS_NO_FREE_PAGES || err == ESP_ERR_NVS_NEW…

openlayer 鼠标点击船舶,打开船舶简单弹框

背景&#xff1a; 对创建的地图对象&#xff0c;可以添加上监听事件&#xff0c;常用的有&#xff1a;地图点击事件、鼠标移动事件。 通过监听这些事件&#xff0c;又可以区分不同图层的不同要素&#xff0c;获取不同数据&#xff1b; 根据这些数据&#xff0c;又可以发起网络请…

每日复盘-20240704

今日关注&#xff1a; 20240704 六日涨幅最大: ------1--------300391--------- 长药控股 五日涨幅最大: ------1--------300391--------- 长药控股 四日涨幅最大: ------1--------300391--------- 长药控股 三日涨幅最大: ------1--------300391--------- 长药控股 二日涨幅最…

HTTP与HTTPS协议区别及应用场景

HTTP&#xff08;超文本传输​​协议&#xff09;和 HTTPS&#xff08;安全超文本传输​​协议&#xff09;都是用于通过网络传输数据的协议。虽然它们有一些相似之处&#xff0c;但在安全性和数据保护方面也存在显著差异。 在这篇博文中&#xff0c;我们将探讨 HTTP 和 HTTPS…

如何避免爬取网站时IP被封?

互联网协议 (IP) 地址是识别网络抓取工具的最常见方式。IP 是每个互联网交换的核心&#xff0c;对其进行跟踪和分析可以了解很多有关连接客户端的信息。 在网络抓取中&#xff0c;IP 跟踪和分析&#xff08;又名指纹&#xff09;通常用于限制和阻止网络抓取程序或其他不需要的访…