为什么在foreach循环中不要对元素进行remove/add操作?

[toc]

前言

在阿里巴巴Java开发手册中,有下面这样的规定:

这篇文章我们就来深入探讨其中的原因。

正文

为什么结果如此不同?

我们先来看看前言中的反例会出现什么意料之外的结果:

----------------------------------------------------------------------------------------------------------------------------------------------------------------------

仅仅是remove的元素不同,为什么会出现如此不同的结果呢?我们反编译上面报错的字节码文件可得:


import java.io.PrintStream;
import java.util.*;

public class Test
{

    public Test()
    {
    }

    public static void main(String args[])
    {
        ArrayList arraylist = new ArrayList();
        arraylist.add("1");
        arraylist.add("2");
        Iterator iterator = arraylist.iterator();
        do
        {
            if(!iterator.hasNext())
                break;        // 1
            String s = (String)iterator.next();         // 2
            if("2".equals(s))
                arraylist.remove(s);
        } while(true);
        System.out.println((new StringBuilder()).append("list:").append(arraylist).toString());
    }
}

通过这个反编译结果我们可以看到foreach底层其实还是使用iterator进行迭代。并且Debug上面的代码,发现当删除"2"元素后,代码执行到2处时报错;但当删除"1"元素后,代码会执行1处代码退出循环,由于没有执行2处的代码,所以删除"1元素"时不会报错。那么有人可能就会问了:为什么删除"2"元素后,1处代码不执行?我们可以通过查看ArrayList的hasNext()的源码找到答案:


class ArrayList {
    private int size;   // The size of the ArrayList (the number of elements it contains).

    private class Itr implements Iterator {
        int cursor;       // index of next element to return
	
	public boolean hasNext() {
	    return cursor != size;
	}
    }
	
}

当删除"1"元素后,cursor值为1("2"元素的下标),size值也为1,两者相等,故hasNext()返回false,所以执行1处代码;但当删除"2"元素后,cursor值为0("1"元素的下标),size值还是为1,两者不相等,故hasNext()返回true,所以无法执行1处代码。故而就出现了上面截然不同的结果。

remove/add方法?

那么为什么在执行2处代码时会报错呢?通过上面的报错信息我们可以看出,ConcurrentModificationException是在执行checkForComodification()的过程抛出的,checkForComodification()的源码如下所示:


final void checkForComodification() {
    if (modCount != expectedModCount)   
        throw new ConcurrentModificationException();
}

上面方法中的modCount是ArrayList中的一个成员变量,它表示该集合实际被修改的次数expectedModCount是ArrayList中的一个内部类Itr(Itr是一个Iterator的实现:使用ArrayList.iterator()获取到的迭代器就是Itr类的实例)中的成员变量,它表示这个迭代器期望该集合被修改的次数,需要注意的是这个值是在集合调用iterator()时初始化,并且只有通过该迭代器对集合进行操作时,该值才会发生改变。

那么remove()/add()为什么会导致这两者的值不等呢?它对集合中的元素是怎样进行操作的呢?查看remove方法的源码:


public E remove(int index) {
    rangeCheck(index);
    checkForComodification();
    E result = parent.remove(parentOffset + index);
    this.modCount = parent.modCount;       
    this.size--;
    return result;
}

由上面的源代码我们可以看到remove()仅对modCount变量进行了操作。于是我们就可以知道:在foreach(即iterator)对集合进行遍历时,元素在"自己"不知不觉的情况下被删除/添加,这时就会抛出异常,提示用户可能发生了并发修改。

如何解决?

明白了为什么之后,我们就需要思考如何去解决它:

  • 使用Iterator提供的remove()。

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

public class Test {
    public static void main(String[] args) {
        List<String> list = new ArrayList<String>();
        list.add("1");
        list.add("2");

        Iterator iterator = list.iterator();
        while(iterator.hasNext()) {
            if("2".equals(iterator.next())) {
                iterator.remove();
            }
        }

        System.out.println("list:" + list);
    }
}

  • 如果是List集合,还可以使用listIterator提供的remove()和add()。

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

public class Test {
    public static void main(String[] args) {
        List<String> list = new ArrayList<String>();
        list.add("1");
        list.add("2");


        Iterator iterator = list.listIterator();
        while(iterator.hasNext()) {
            if("2".equals(iterator.next())) {
                iterator.remove();
//                ((ListIterator) iterator).add("3");
            }
        }

        System.out.println("list:" + list);
    }
}

  • 使用Java8提供的filter进行过滤:在Java8中可以把集合转换成流,并且对于流有一种filter操作,它可以对原始Stream进行某项过滤,通过过滤的元素被留下来生成一个新Stream。

import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

public class Test {
    public static void main(String[] args) {
        List<String> list = new ArrayList<String>() {{
            add("1");
            add("2");
        }};

        list = list.stream().filter(name -> ! name.equals("2")).collect(Collectors.toList());
        System.out.println("list:" + list);
    }
}

猜你喜欢

转载自www.cnblogs.com/syhyfh/p/12551092.html