ava中的ArrayList循环遍历并且删除元素时经常不小心掉坑里,昨天又碰到了,感觉有必要单独写篇文章记一下。

先写个测试代码:

  1. import java.util.ArrayList;
  2. public class ArrayListRemove {
  3. public static void main(String[] args) {
  4. ArrayList<String> list = new ArrayList<String>();
  5. list.add("a");
  6. list.add("bb");
  7. list.add("bb");
  8. list.add("ccc");
  9. list.add("ccc");
  10. list.add("ccc");
  11. remove(list);
  12. for (String s : list) {
  13. System.out.println("element : " + s);
  14. }
  15. }
  16. public static void remove(ArrayList<String> list) {
  17. // TODO:
  18. }
  19. }

错误写法示例一:

  1. public static void remove(ArrayList<String> list) {
  2. for (int i = 0; i < list.size(); i++) {
  3. String s = list.get(i);
  4. if (s.equals("bb")) {
  5. list.remove(s);
  6. }
  7. }
  8. }

这种最普通的循环写法执行后会发现有一个“bb”的字符串没有删掉。

错误写法示例二:

  1. public static void remove(ArrayList<String> list) {
  2. for (String s : list) {
  3. if (s.equals("bb")) {
  4. list.remove(s);
  5. }
  6. }
  7. }

这种for each写法会发现报出著名的并发修改异常java.util.ConcurrentModificationException。

要分析产生上述错误现象的原因唯有翻一翻jdk的ArrayList源码,先看下ArrayList中的remove方法(注意ArrayList中的remove有两个同名方法,只是入参不同,这里看的是入参为Object的remove方法)是怎么实现的:

  1. public boolean remove(Object o) {
  2. if (o == null) {
  3. for (int index = 0; index < size; index++)
  4. if (elementData[index] == null) {
  5. fastRemove(index);
  6. return true;
  7. }
  8. } else {
  9. for (int index = 0; index < size; index++)
  10. if (o.equals(elementData[index])) {
  11. fastRemove(index);
  12. return true;
  13. }
  14. }
  15. return false;
  16. }

按一般执行路径会走到else路径下最终调用faseRemove方法:

  1. private void fastRemove(int index) {
  2. modCount++;
  3. int numMoved = size - index - 1;
  4. if (numMoved > 0)
  5. System.arraycopy(elementData, index+1, elementData, index,
  6. numMoved);
  7. elementData[--size] = null; // Let gc do its work
  8. }

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

针对这种情况可以倒序删除的方式来避免:

  1. public static void remove(ArrayList<String> list) {
  2. for (int i = list.size() - 1; i >= 0; i--) {
  3. String s = list.get(i);
  4. if (s.equals("bb")) {
  5. list.remove(s);
  6. }
  7. }
  8. }

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

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

  1. public Iterator<E> iterator() {
  2. return new Itr();
  3. }

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

  1. public E next() {
  2. checkForComodification();
  3. try {
  4. E next = get(cursor);
  5. lastRet = cursor++;
  6. return next;
  7. } catch (IndexOutOfBoundsException e) {
  8. checkForComodification();
  9. throw new NoSuchElementException();
  10. }
  11. }

第一行checkForComodification方法:

  1. final void checkForComodification() {
  2. if (modCount != expectedModCount)
  3. throw new ConcurrentModificationException();
  4. }

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

  1. public static void remove(ArrayList<String> list) {
  2. Iterator<String> it = list.iterator();
  3. while (it.hasNext()) {
  4. String s = it.next();
  5. if (s.equals("bb")) {
  6. it.remove();
  7. }
  8. }
  9. }

Java中ArrayList循环遍历并删除元素的陷阱的更多相关文章

  1. 【转】ArrayList循环遍历并删除元素的常见陷阱

    转自:https://my.oschina.net/u/2249714/blog/612753?p=1 在工作和学习中,经常碰到删除ArrayList里面的某个元素,看似一个很简单的问题,却很容易出b ...

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

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

  3. ArrayList循环遍历并删除元素的几种情况

    如下代码,想要循环删除列表中的元素b,该怎么处理? public class ListDemo { public static void main(String[] args) { ArrayList ...

  4. Java中List循环遍历的时候删除当前对象(自己)

    方法一 Java代码   ArrayList<String> list = new ArrayList<String>();           list.add(" ...

  5. Java HashMap 如何正确遍历并删除元素

    (一)HashMap的遍历 HashMap的遍历主要有两种方式: 第一种采用的是foreach模式,适用于不需要修改HashMap内元素的遍历,只需要获取元素的键/值的情况. HashMap<K ...

  6. Java中ArrayList边遍历边修改

    用for-each 边遍历ArrayList 边修改时: public static void main(String[] args) { ArrayList<String> list = ...

  7. Java中for循环遍历List的两种方法

    我们平常使用的方法: List<WebElement> element = driver.findElements(By.tagName("input"));      ...

  8. 【Java】List遍历时删除元素的正确方式

    当要删除ArrayList里面的某个元素,一不注意就容易出bug.今天就给大家说一下在ArrayList循环遍历并删除元素的问题.首先请看下面的例子: import java.util.ArrayLi ...

  9. 关于java中ArrayList的快速失败机制的漏洞——使用迭代器循环时删除倒数第二个元素不会报错

    一.问题描述 话不多说,先上代码: public static void main(String[] args) throws InterruptedException { List<Strin ...

随机推荐

  1. BZOJ2588Count on a tree——LCA+主席树

    题目描述 给定一棵N个节点的树,每个点有一个权值,对于M个询问(u,v,k),你需要回答u xor lastans和v这两个节点间第K小的点权.其中lastans是上一个询问的答案,初始为0,即第一个 ...

  2. 洛谷P1516 青蛙的约会(扩展欧几里德)

    洛谷题目传送门 很容易想到,如果他们相遇,他们初始的位置坐标之差\(x-y\)和跳的距离\((n-m)t\)(设\(t\)为跳的次数)之差应该是模纬线长\(l\)同余的,即\((n-m)t\equiv ...

  3. BZOJ4008 : [HNOI2015]亚瑟王(期望dp)

    题意 略(看了20min才看懂...) 题解 我一开始天真地一轮轮推期望,发现根本不好算... 唉~ 不会做就只能抄题解咯 看了一波DOFY大佬的解法qwq 发现有句神奇的话 记住,期望要倒着推... ...

  4. 沉迷Link-Cut tree无法自拔之:[BZOJ3514] Codechef MARCH14 GERALD07 加强版

    来自蒟蒻 \(Hero \_of \_Someone\) 的 \(LCT\) 学习笔记 $ $ 又是一道骚题...... 先讲一个结论: 假设我们用 \(LCT\) 来做这道题, 在插入边 \(i\) ...

  5. PendingIntent的使用

    1, 构造intent Intent mIntent = new Intent("android.intent.action.MAIN"); ComponentName comp ...

  6. Luogu 1081 【NOIP2012】开车旅行 (链表,倍增)

    Luogu 1081 [NOIP2012]开车旅行 (链表,倍增) Description 小A 和小B决定利用假期外出旅行,他们将想去的城市从1到N 编号,且编号较小的城市在编号较大的城市的西边,已 ...

  7. Libre 6011 「网络流 24 题」运输问题 (网络流,最小费用最大流)

    Libre 6011 「网络流 24 题」运输问题 (网络流,最小费用最大流) Description W 公司有m个仓库和n个零售商店.第i个仓库有\(a_i\)个单位的货物:第j个零售商店需要\( ...

  8. [poj3046][Ant counting数蚂蚁]

    题目链接 http://noi.openjudge.cn/ch0206/9289/ 描述 Bessie was poking around the ant hill one day watching ...

  9. window.open打开页面居中显示

    <script type="text/javascript"> function openwindow(url,name,iWidth,iHeight) { var u ...

  10. (转)小谈keepalived vip漂移原理与VRRP协议

    背景:之前搭建过keepalived双机热备的集群,但对其中的原理不甚理解,看完就忘了,所有有必要深入的学习下. 简介 什么是keepalived呢?keepalived是实现高可用的一种轻量级的技术 ...