zoukankan      html  css  js  c++  java
  • foreach循环中为什么不要进行remove/add操作

    先来看一段代码,摘自阿里巴巴的java开发手册

    1 List<String> a = new ArrayList<String>();
    2  a.add("1");
    3  a.add("2");
    4  for (String temp : a) {
    5      if("1".equals(temp)){
    6          a.remove(temp);
    7 } 
    8 }

    此时执行代码,没有问题,但是需要注意,循环此时只执行了一次。具体过程后面去分析。再来看一段会出问题的代码:

    List<String> a = new ArrayList<String>();
     a.add("1");
     a.add("2");
     for (String temp : a) {
         if("2".equals(temp)){
             a.remove(temp);
    } 
    }

    输出为:

    Exception in thread "main" java.util.ConcurrentModificationException
    at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:859)
    at java.util.ArrayList$Itr.next(ArrayList.java:831)
    at luyudepackage.waitTest.main(waitTest.java:57)

    是不是很奇怪?接下来将class文件,反编译下,结果如下

     1 List a = new ArrayList();
     2 a.add("1");
     3 a.add("2");
     4 Iterator i$ = a.iterator();
     5 do
     6 {
     7     if(!i$.hasNext())
     8         break;
     9     String temp = (String)i$.next();
    10     if("1".equals(temp))
    11         a.remove(temp);
    12 } while(true);

    几个需要注意的点:

    1.foreach遍历集合,实际上内部使用的是iterator。

    2.代码先判断是否hasNext,然后再去调用next,这两个函数是引起问题的关键。

    3.这里的remove还是list的remove方法。

    先去观察下list.remove()方法中的核心方法fastRemove()方法。

    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; // clear to let GC do its work
    8     }

    注意第二行,modCount++,此处先不表,下文再说这个参数。

    顺路观察下list.add()方法

    1 public boolean add(E e) {
    2         ensureCapacityInternal(size + 1);  // Increments modCount!!
    3         elementData[size++] = e;
    4         return true;
    5     }

    注意第二行的注释,说明这个方法也会使modCount++

    再去观察下,iterator()方法

    1 public Iterator<E> iterator() {
    2         return new Itr();
    3  }
     1 private class Itr implements Iterator<E> {
     2         int cursor;       // index of next element to return
     3         int lastRet = -1; // index of last element returned; -1 if no such
     4         int expectedModCount = modCount;
     5 
     6         public boolean hasNext() {
     7             return cursor != size;
     8         }
     9 
    10         @SuppressWarnings("unchecked")
    11         public E next() {
    12             checkForComodification();//万恶之源
    13             int i = cursor;
    14             if (i >= size)
    15                 throw new NoSuchElementException();
    16             Object[] elementData = ArrayList.this.elementData;
    17             if (i >= elementData.length)
    18                 throw new ConcurrentModificationException();
    19             cursor = i + 1;
    20             return (E) elementData[lastRet = i];
    21         }
    22 
    23         public void remove() {
    24             if (lastRet < 0)
    25                 throw new IllegalStateException();
    26             checkForComodification();
    27 
    28             try {
    29                 ArrayList.this.remove(lastRet);
    30                 cursor = lastRet;
    31                 lastRet = -1;
    32                 expectedModCount = modCount;
    33             } catch (IndexOutOfBoundsException ex) {
    34                 throw new ConcurrentModificationException();
    35             }
    36         }
    37 
    38         final void checkForComodification() {
    39             if (modCount != expectedModCount)
    40                 throw new ConcurrentModificationException();
    41         }
    42     }

    几个需要注意的点:

    1.在iterator初始化的时候(也就是for循环开始处),expectedModCount = modCount,猜测是和当时list内部的元素数量有关系(已证实)。

    2.当cursor != size的时候,hasNext返回true

    3.next()函数的第一行,checkForComodification()这个函数就是报错的原因 这个函数就是万恶之源

    4.第39行,mod != expectedModCount 就会抛出ConcurrentModificationException()


     接下来分析文章开头的第一个例子,为啥不会报错?

    第一个例子执行完第一次循环后,mod = 3 expectedModCount =2 cursor = 1 size = 1  所以程序在执行hasNext()的时候会返回false,所以程序不会报错。

    第二个例子执行完第二次循环后,mod = 3 expectdModCount = 2 cursor = 2 size = 1 此时cursor != size 程序认定还有元素,继续执行循环,调用next方法但是此时mod != expectedModCount 所以此时会报错。

    道理我们都懂了,再看一个例子

     1 public static void main(String[] args) throws Exception {
     2         List<String> a = new ArrayList<String>();
     3         a.add("1");
     4         a.add("2");
     5         for (String temp : a) {
     6             System.out.println(temp);
     7             if("2".equals(temp)){
     8                 a.add("3");
     9                 a.remove("2");
    10             } 
    11         }
    12 }

    此时输出为:

    1

    2

    显然,程序并没有执行第三次循环,第二次循环结束,cursor再一次等于size,程序退出循环。

    与remove类似,将文章开头的代码中remove替换为add,我们会发现无论是第一个例子还是第二个例子,都会抛出ConcurrentModificationException错误。

    原因同上,代码略。


    手册上推荐的代码如下

    1 Iterator<String> it = a.iterator(); while(it.hasNext()){
    2 String temp = it.next(); if(删除元素的条件){
    3         it.remove();
    4        }
    5 }

    此时remove是iterator的remove,我们看一下它的源码:

     1  public void remove() {
     2             if (lastRet < 0)
     3                 throw new IllegalStateException();
     4             checkForComodification();
     5 
     6             try {
     7                 ArrayList.this.remove(lastRet);
     8                 cursor = lastRet;   //index of last element returned;-1 if no such
     9                 lastRet = -1;
    10                 expectedModCount = modCount;
    11             } catch (IndexOutOfBoundsException ex) {
    12                 throw new ConcurrentModificationException();
    13             }
    14         }

    注意第10行,第8行,所以此时程序不会有之前的问题。

    但是手册上推荐的方法,在多线程环境还是有可能出现问题,一个线程执行上面的代码,一个线程遍历迭代器中的元素,同样会抛出CocurrentModificationException。

    如果要并发操作,需要对iterator对象加锁。


    平时遍历list,然后删除某个元素的时候,如果仅仅删除第一个且删除之后调用break  //代表着此时不会再去执行iterator.next方法 也就不会触发万恶之源

    而如果要删除所有的某元素,则会报错,谨记!

     Ps再来看一个佐证

    public static void main(String[] args) {
                ArrayList<Integer> list = new ArrayList<>();
                list.add(1);
                list.add(2);
                list.add(3);
                for(int i : list){
                    System.out.println(i);
                    if(i == 2){
                        list.remove((Object)2);
                    }
                }
    
            }

    此时只会输出

    1

    2

    当把remove对象改为3时候,再次报错。

  • 相关阅读:
    js对象数组(JSON) 根据某个共同字段 分组
    一个 函数 用来转化esSearch 的range 条件
    关于 vuex 报错 Do not mutate vuex store state outside mutation handlers.
    android listview 重用view导致的选择混乱问题
    android SDK和ADT的更新
    Android中adb push和adb install的使用区别
    pycharm中添加扩展工具pylint
    su Authentication failure解决
    Putty以及adb网络调试
    有关android源码编译的几个问题
  • 原文地址:https://www.cnblogs.com/luyu1993/p/7148765.html
Copyright © 2011-2022 走看看