Java 基础 - List 遍历时为什么不能通过 for 循环进行删除,而使用 Iterator 可以 ?

本文详细探讨了在Java中遍历List时进行添加和删除操作的各种情况,包括普通for循环、增强for循环以及使用Iterator。通过源码分析,解释了为何在不同遍历方式下操作可能导致数据遗漏或抛出`ConcurrentModificationException`异常。同时,介绍了Iterator和ListIterator在遍历过程中的工作原理,以及如何安全地进行数据操作。
摘要由CSDN通过智能技术生成

说明

List 在遍历时可以进行添加,删除操作吗?为什么?Iterator 是什么?可以进行上述操作吗?知道底层原理吗?

以上问题是我在面试时碰到的,在本篇博文中,我将通过源码对 List 遍历时的添加,删除操作的相关知识点进行总结。

注意,这里的操作都是针对正在遍历的 List 自身的操作。

首先,回答以上问题:

  • 在普通的 for 循环中,可以进行数据的添加操作,但不能进行删除操作。

  • 在增强的 for 循环中,既不能进行添加操作,也不能进行删除操作。

  • 通过 Iterator 及相关扩展类,可以进行添加或删除操作。

接下来,我将通过示例和源码来解释为什么。

正文

普通 for 循环

在普通 for 循环中,可以进行数据的添加,但是不能删除删除。原因是在删除时,由于坐标值的增加,会导致数据遗漏。

示例:

public static void main(String[] args) {
   
    List<Integer> nums = new ArrayList<>();
    nums.add(1);
    nums.add(2);
    nums.add(3);
    nums.add(4);
    for (int i = 0; i < nums.size(); i++) {
   
        Integer num = nums.get(i);
        System.out.println(num);
        if (num.equals(2)) {
   
            nums.remove(num);
        }
    }
}
---- 输出 ----
1
2
4

可以看到,在删除 2 后,下一个元素 3 在遍历时被遗漏了。

这是因为在删除 2 后,剩余的元素会整体向前移动一位,而坐标值仍是递增的,所以下一个坐标元素值相当于当前删除元素的下下一位元素值。对此,我们可以在删除元素时,将坐标值减 1。

if (num.equals(2)) {
   
    nums.remove(num);
    i--;
}

List.remove

在以上示例中,我使用的是 ArrayList.remove(Object o) 方法,该方法会移除 List 中第一个满足目标值的元素。

// Removes the first occurrence of the specified element from this list, if it is present.
public boolean remove(Object o) {
   
    if (o == null) {
   
        for (int index = 0; index < size; index++)
            if (elementData[index] == null) {
   
                fastRemove(index);
                return true;
            }
    } else {
   
        for (int index = 0; index < size; index++)
            if (o.equals(elementData[index])
在多线程环境中,正确地遍历List集合并且保证性能和数据安全性是关键。Iterator和for循环在多线程环境下的性能及安全注意事项如下: 参考资源链接:[Java List遍历方式性能比较:Iterator、for循环与多线程注意事项](https://wenku.csdn.net/doc/3zhzjpytac?spm=1055.2569.3001.10343) 对于Iterator遍历,它的实现往往与List的内部结构紧密相关。使用Iterator进行遍历,可以安全地在遍历过程中删除元素,但如果尝试在迭代过程中修改List(如添加或删除元素),则必须抛出`ConcurrentModificationException`异常。在多线程环境下,如果多个线程同修改同一个ListIterator不能保证数据的一致性。因此,当使用Iterator进行遍历,如果涉及到多线程修改List,则需要额外的同步控制。 对于for循环遍历,通过索引访问List元素的方式具有较高的效率,因为它避免了迭代器的开销。然而,如果List的内容在遍历过程中被其他线程修改,那么可能会导致数组越界或其他不可预见的问题。在多线程环境中,应确保只有一个线程能够修改List,或者使用其他机制(如读写锁)来控制对List的访问,以保证遍历过程的安全性。 选择使用哪种遍历方式,需要根据具体的应用场景来决定。例如,如果操作主要是读取且List不会被修改,那么for循环遍历将是最佳选择。若需要在遍历的同修改List使用Iterator会更安全,但需注意,如果在多线程环境中操作List,仍需考虑额外的同步机制来避免线程安全问题。 实际上,Java并发包中的`CopyOnWriteArrayList`等线程安全的集合类,可以在多线程环境下提供更安全的遍历方式。`CopyOnWriteArrayList`通过在每次修改操作复制底层数组,从而避免了并发修改的问题,使得遍历操作可以在不加锁的情况下安全执行。 为了更深入地了解不同遍历方式在多线程环境下的性能和安全特性,建议参阅《Java List遍历方式性能比较:Iterator、for循环与多线程注意事项》。该资料详细比较了不同遍历方式的性能,同也提供了多线程环境下对List操作的安全性分析,对于解决当前问题将是一个宝贵的资源。 参考资源链接:[Java List遍历方式性能比较:Iterator、for循环与多线程注意事项](https://wenku.csdn.net/doc/3zhzjpytac?spm=1055.2569.3001.10343)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值