`

ArrayList循环遍历并删除元素的常见陷阱

    博客分类:
  • java
 
阅读更多

在工作和学习中,经常碰到删除ArrayList里面的某个元素,看似一个很简单的问题,却很容易出bug。不妨把这个问题当做一道面试题目,我想一定能难道不少的人。今天就给大家说一下在ArrayList循环遍历并删除元素的问题。首先请看下面的例子:

 

 

如果要想删除list的b字符,有下面两种常见的错误例子:

错误写法实例一:

 

 

错误的原因:这种最普通的循环写法执行后会发现第二个“b”的字符串没有删掉。

错误写法实例二:

 

 

错误的原因:这种for-each写法会报出著名的并发修改异常:java.util.ConcurrentModificationException。

先解释一下实例一的错误原因。翻开JDK的ArrayList源码,先看下ArrayList中的remove方法(注意ArrayList中的remove有两个同名方法,只是入参不同,这里看的是入参为Object的remove方法)是怎么实现的:

 

 

一般情况下程序的执行路径会走到else路径下最终调用faseRemove方法:

 

 

可以看到会执行System.arraycopy方法,导致删除元素时涉及到数组元素的移动。针对错误写法一,在遍历第一个字符串b时因为符合删除条件,所以将该元素从数组中删除,并且将后一个元素移动(也就是第二个字符串b)至当前位置,导致下一次循环遍历时后一个字符串b并没有遍历到,所以无法删除。针对这种情况可以倒序删除的方式来避免:

 

 

因为数组倒序遍历时即使发生元素删除也不影响后序元素遍历。

接着解释一下实例二的错误原因。错误二产生的原因却是foreach写法是对实际的Iterable、hasNext、next方法的简写,问题同样处在上文的fastRemove方法中,可以看到第一行把modCount变量的值加一,但在ArrayList返回的迭代器(该代码在其父类AbstractList中):

 

 

这里返回的是AbstractList类内部的迭代器实现private class Itr implements Iterator,看这个类的next方法:

 

 

第一行checkForComodification方法:

 

 

这里会做迭代器内部修改次数检查,因为上面的remove(Object)方法修改了modCount的值,所以才会报出并发修改异常。要避免这种情况的出现则在使用迭代器迭代时(显示或for-each的隐式)不要使用ArrayList的remove,改为用Iterator的remove即可。

 

分享到:
评论

相关推荐

    java基础for循环练习题

    对于集合框架,如ArrayList、LinkedList等,增强型`for`循环是首选,它能简洁地遍历集合中的所有元素。如果需要对集合进行更复杂的操作,比如删除元素,则可能需要使用基本的`for`循环配合迭代器。 5. **其他用法*...

    浅谈foreach写失效的问题

    例如,在上面的代码中,开发者尝试使用 foreach 循环来遍历 ArrayList,並在遍历过程中修改元素的值。但是,实际输出的结果却不是预期的结果。这是因为,foreach 循环中的迭代变量 temp 只是一个局部变量,是集合...

    java ArrayList.remove()的三种错误用法以及六种正确用法详解

    在for循环中直接删除元素可能导致遍历过程中的索引错乱。例如,当删除一个元素后,未更新的索引将指向下一个元素,但实际上,由于删除,这个位置已被前一个元素占用。例如: ```java List<Long> list = new ...

    详解JAVA中的for-each循环与迭代

    例如,在上述代码中,我们创建了一个`ArrayList`并获取了一个迭代器,然后在`while`循环中尝试使用`list.remove()`删除元素,这违反了迭代器的规则,因此抛出了异常。正确做法是使用迭代器的`remove()`方法删除元素...

    java集合框架全面进阶

    例如,了解HashMap的扩容机制、LinkedList的节点操作、ArrayList的动态数组管理等,能够帮助开发者在设计和实现自己的数据结构时避免常见陷阱。 6. **工具类**:Collections和Arrays类提供了很多实用的静态方法,如...

    突破程序员基本功的16课.part2

    迭代时删除指定元素 3.5 小结 第4课 Java的内存回收 4.1 Java引用的种类 4.1.1 对象在内存中状态 4.1.2 强引用 4.1.3 软引用 4.1.4 弱引用 4.1.5 虚引用 4.2 Java的内存泄漏 4.3 垃圾回收机制 4.3.1 垃圾...

    java集合迭代器Iterator中的remove陷阱

    5. **遍历安全**:为了避免并发修改异常,可以考虑使用`Iterator`的增强型for循环(foreach)来遍历集合,这样在遍历时删除元素会更安全,因为编译器会自动处理迭代器的同步问题。 ```java for (String element : ...

    避坑手册 - JAVA编码中容易踩坑的十大陷阱.doc

    当你需要在遍历List的同时删除元素时,直接使用for-each循环或者索引循环是错误的。这两种方式都会导致`ConcurrentModificationException`。正确的做法是使用迭代器(Iterator)进行删除,或者使用Java 8的Lambda...

    35个Java代码性能优化总结.pdf

    ArrayList在随机访问时表现更佳,而LinkedList在插入和删除操作时更优。应根据实际情况选择合适的集合类型。 #### 4. I/O操作优化 在处理I/O操作时,应使用BufferedReader和BufferedWriter来减少对磁盘的读写次数,...

    java程序中容易出错的地方

    - **ArrayList vs LinkedList**:对于频繁插入删除的场景,应该选择`LinkedList`;而如果主要是元素的读取操作,则`ArrayList`更合适。 - **空指针异常**:在向集合中添加元素之前,务必检查该元素是否为null,以...

    Java开发人员最常犯的10个错误

    在Java开发过程中,开发者经常会...这些错误揭示了Java编程中的一些常见陷阱,理解并避免它们能提升代码质量、性能和稳定性。作为Java开发者,应该时刻关注这些最佳实践,不断学习和改进,以编写更高效、更可靠的代码。

    Java基础训练源码

    源码可能涉及各种集合操作,如添加、删除、遍历和查找元素,以及集合间的转换。 5. **泛型**:泛型提供了一种在编译时确保类型安全的方式,防止在容器中存储不兼容的数据类型。源码将展示如何定义和使用泛型类、...

    java编程事项(转载收集整理版)

    熟练掌握它们的操作,如添加、删除、遍历以及查找元素,可以大大提高代码效率。 5. **多线程**:Java提供了内置的多线程支持,通过Thread类和Runnable接口可以创建并管理线程。理解线程同步(如synchronized关键字...

    board-collection

    1. **集合框架基础**:理解Collection接口是所有单值容器的根接口,而Iterable接口使得可以使用for-each循环遍历集合。了解ArrayList和LinkedList的不同,比如ArrayList更适合随机访问,而LinkedList适合频繁插入和...

    java代码-数组 40 李启强

    - 使用for循环遍历数组是最常见的方法,例如`for(int i=0; i; i++)`。 - Java 5及以上版本引入增强型for循环(foreach),简化了遍历过程,如`for(int num : numbers)`。 4. **多维数组**: - Java支持二维及...

    JAVA基础易错总结(50%你不知道)

    同时,理解迭代器的工作原理,避免在遍历集合时修改集合,这些都是避免运行时错误的关键。 并发编程是Java的强项,但也是挑战。线程安全的编程需要理解synchronized、volatile关键字的作用,以及死锁、活锁、饥饿等...

    Java中的内存泄漏详解编程小技巧共3页.pdf.zip

    总的来说,理解Java内存管理和垃圾收集机制,以及识别和避免内存泄漏的常见陷阱,是提高程序性能和稳定性的重要步骤。通过定期的内存分析和优化实践,开发者可以有效地防止和解决Java中的内存泄漏问题,提升应用的...

    curso-dio-intro-collections

    5. **迭代器和泛型遍历**:学习如何使用迭代器遍历集合,以及Java 5引入的foreach循环(增强型for循环)对集合的便捷遍历。 6. **集合操作**:课程会涵盖集合的基本操作,如添加、删除、查找元素,以及集合之间的...

    Java程序员面试宝典视频课程之数组(五)

    例如,用for循环遍历数组: ```java for (int i = 0; i ; i++) { System.out.println(numbers[i]); } ``` 数组的操作还包括查找和排序。线性查找是最基础的方法,但效率较低。二分查找适用于已排序的数组,效率更...

Global site tag (gtag.js) - Google Analytics