• foreach循环中为什么不要进行remove/add操作


    https://www.cnblogs.com/luyu1993/p/7148765.html

    先来看一段代码,摘自阿里巴巴的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时候,再次报错。

    才能这种东西 本来就是靠自己挖掘创造的 我也不是什么天才 我只是比任何人都拼命工作 一步一个脚印走过来了 等我回头一看 背后没有一个身影 那帮懒惰的人在山脚念叨着 谁叫那家伙是天才 开什么玩笑 我最讨厌悠哉悠哉长大的慢性子 比我有时间 有精力 感情丰富的人 为什么比我懒惰 那就给我啊 要把这些东西都浪费掉的话 就通通给我 我还有很多很多想创造的东西 给我啊--------摘自《legal high II》 与诸君共勉
  • 相关阅读:
    进位的算法
    java操作mysql中的编码问题解决
    ajax
    java.lang.NoClassDefFoundError: org/objectweb/asm/Type
    java.lang.NoSuchFieldError: deferredExpression
    java.lang.NoClassDefFoundError: antlr/ANTLRException
    Missing message for key "err1" in bundle "(default bundle)" for locale zh_CN
    接口和抽象类
    overload和override
    public,protected,friendly,private的访问权限
  • 原文地址:https://www.cnblogs.com/steve-jiang/p/12172296.html
Copyright © 2020-2023  润新知