从ConcurrentModificationException到 list remove

本文详细解析了Java List遍历时并发修改导致的ConcurrentModificationException异常产生的原因及解决方案,包括Iterator的工作原理、异常抛出机制、避免方法等,并通过实例展示了如何正确地在遍历时移除元素而不引发异常。
</pre><p><span style="font-family:verdana,Arial,helvetica,sans-seriff; font-size:13px; line-height:20.799999237060547px; color:rgb(0,0,255)"><span style="color:rgb(75,75,75); font-family:verdana,Arial,helvetica,sans-seriff; font-size:13px; line-height:20.799999237060547px">工作中碰到个ConcurrentModificationException。代码如下:</span></span></p><p><span style="font-family:verdana,Arial,helvetica,sans-seriff; font-size:13px; line-height:20.799999237060547px; color:rgb(0,0,255)">List list = ...;</span></p><span style="font-family:verdana,Arial,helvetica,sans-seriff; font-size:13px; line-height:20.799999237060547px; color:rgb(0,0,255)">for(Iterator iter = list.iterator(); iter.hasNext();) {<br style="" />    Object obj = iter.next();<br style="" />    ...<br style="" />    if(***) {<br style="" />        list.remove(obj);<br style="" />    }<br style="" />}<br style="" /></span><span style="color:rgb(75,75,75); font-family:verdana,Arial,helvetica,sans-seriff; font-size:13px; line-height:20.799999237060547px">在执行了remove方法之后,再去执行循环,iter.next()的时候,报java.util.ConcurrentModificationException(当然,如果remove的是最后一条,就不会再去执行next()操作了)</span><br style="color:rgb(75,75,75); font-family:verdana,Arial,helvetica,sans-seriff; font-size:13px; line-height:20.799999237060547px" /><br style="color:rgb(75,75,75); font-family:verdana,Arial,helvetica,sans-seriff; font-size:13px; line-height:20.799999237060547px" /><span style="color:rgb(75,75,75); font-family:verdana,Arial,helvetica,sans-seriff; font-size:13px; line-height:20.799999237060547px">下面来看一下源码</span><br style="color:rgb(75,75,75); font-family:verdana,Arial,helvetica,sans-seriff; font-size:13px; line-height:20.799999237060547px" /><span style="font-family:verdana,Arial,helvetica,sans-seriff; font-size:13px; line-height:20.799999237060547px; color:rgb(0,0,255)">public interface Iterator<E> {<br style="" />    boolean hasNext();<br style="" />    E next();<br style="" />    void remove();<br style="" />}<br style="" /><br style="" />public interface Collection<E> extends Iterable<E> {<br style="" />    ...<br style="" />    Iterator<E> iterator();<br style="" />    boolean add(E o);<br style="" />    boolean remove(Object o);<br style="" />    ...<br style="" />}</span><br style="color:rgb(75,75,75); font-family:verdana,Arial,helvetica,sans-seriff; font-size:13px; line-height:20.799999237060547px" /><span style="color:rgb(75,75,75); font-family:verdana,Arial,helvetica,sans-seriff; font-size:13px; line-height:20.799999237060547px">这里有两个remove方法</span><br style="color:rgb(75,75,75); font-family:verdana,Arial,helvetica,sans-seriff; font-size:13px; line-height:20.799999237060547px" /><br style="color:rgb(75,75,75); font-family:verdana,Arial,helvetica,sans-seriff; font-size:13px; line-height:20.799999237060547px" /><span style="color:rgb(75,75,75); font-family:verdana,Arial,helvetica,sans-seriff; font-size:13px; line-height:20.799999237060547px">接下来来看看AbstractList</span><br style="color:rgb(75,75,75); font-family:verdana,Arial,helvetica,sans-seriff; font-size:13px; line-height:20.799999237060547px" /><span style="font-family:verdana,Arial,helvetica,sans-seriff; font-size:13px; line-height:20.799999237060547px; color:rgb(0,0,255)">public abstract class AbstractList<E> extends AbstractCollection<E> implements List<E> {  <br style="" />//AbstractCollection和List都继承了Collection<br style="" />    protected transient int modCount = 0;<br style="" />    private class Itr implements Iterator<E> {  //内部类Itr<br style="" />        int cursor = 0;<br style="" />        int lastRet = -1;<br style="" />        int expectedModCount = modCount;<br style="" /><br style="" />        public boolean hasNext() {<br style="" />            return cursor != size();<br style="" />        }<br style="" /><br style="" />        public E next() {<br style="" />            checkForComodification();  <span style="color:rgb(255,0,0)">//特别注意这个方法<br style="" /></span>            try {<br style="" />                E next = get(cursor);<br style="" />                lastRet = cursor++;<br style="" />                return next;<br style="" />            } catch(IndexOutOfBoundsException e) {<br style="" />                checkForComodification();<br style="" />                throw new NoSuchElementException();<br style="" />            }<br style="" />        }<br style="" /><br style="" />        public void remove() {<br style="" />            if (lastRet == -1)<br style="" />                throw new IllegalStateException();<br style="" />            checkForComodification();<br style="" /><br style="" />            try {<br style="" />                AbstractList.this.remove(lastRet);  <span style="color:rgb(255,0,0)">//执行remove对象的操作<br style="" /></span>                if (lastRet < cursor)<br style="" />                    cursor--;<br style="" />                lastRet = -1;<br style="" />                expectedModCount = modCount;  <span style="color:rgb(255,0,0)">//重新设置了expectedModCount的值,避免了ConcurrentModificationException的产生<br style="" /></span>            } catch(IndexOutOfBoundsException e) {<br style="" />                throw new ConcurrentModificationException();<br style="" />            }<br style="" />        }<br style="" /><br style="" />        final void checkForComodification() {<br style="" />            if (modCount != expectedModCount)  <span style="color:rgb(255,0,0)">//当expectedModCount和modCount不相等时,就抛出ConcurrentModificationException</span><br style="" />                throw new ConcurrentModificationException();<br style="" />        }<br style="" />    }    <br style="" />}</span><br style="color:rgb(75,75,75); font-family:verdana,Arial,helvetica,sans-seriff; font-size:13px; line-height:20.799999237060547px" /><br style="color:rgb(75,75,75); font-family:verdana,Arial,helvetica,sans-seriff; font-size:13px; line-height:20.799999237060547px" /><span style="color:rgb(75,75,75); font-family:verdana,Arial,helvetica,sans-seriff; font-size:13px; line-height:20.799999237060547px">remove(Object o)在ArrayList中实现如下:</span><br style="color:rgb(75,75,75); font-family:verdana,Arial,helvetica,sans-seriff; font-size:13px; line-height:20.799999237060547px" /><span style="font-family:verdana,Arial,helvetica,sans-seriff; font-size:13px; line-height:20.799999237060547px; color:rgb(0,0,255)">public boolean remove(Object o) {<br style="" />    if (o == null) {<br style="" />            for (int index = 0; index < size; index++)<br style="" />        if (elementData[index] == null) {<br style="" />            fastRemove(index);<br style="" />            return true;<br style="" />        }<br style="" />    } else {<br style="" />        for (int index = 0; index < size; index++)<br style="" />            if (o.equals(elementData[index])) {<br style="" />                fastRemove(index);<br style="" />                return true;<br style="" />            }<br style="" />    }<br style="" />    return false;<br style="" />}<br style="" />private void fastRemove(int index) {<br style="" />    modCount++;  <span style="color:rgb(255,0,0)">//只增加了modCount<br style="" /></span>    ....<br style="" />}<br style="" /><span style="color:rgb(51,153,102)"><br style="" /><span style="color:rgb(128,0,0)">所以,产生ConcurrentModificationException的原因就是:<br style="" />执行remove(Object o)方法之后,modCount和expectedModCount不相等了。然后当代码执行到next()方法时,判断了checkForComodification(),发现两个数值不等,就抛出了该Exception。<br style="" />要避免这个Exception,就应该使用remove()方法。</span></span><span style=""><br style="" /></span></span><span style="color:rgb(75,75,75); font-family:verdana,Arial,helvetica,sans-seriff; font-size:13px; line-height:20.799999237060547px">这里我们就不看add(Object o)方法了,也是同样的原因,但没有对应的add()方法。一般嘛,就另建一个List了</span><br style="color:rgb(75,75,75); font-family:verdana,Arial,helvetica,sans-seriff; font-size:13px; line-height:20.799999237060547px" /><p>同理以下的方法也是错误的:</p><p>for(String s : strList){            strList.remove(s);//这种方法是错误的。ConcurrentModificationException    }</p><p></p><br style="color:rgb(75,75,75); font-family:verdana,Arial,helvetica,sans-seriff; font-size:13px; line-height:20.799999237060547px" /><span style="color:rgb(75,75,75); font-family:verdana,Arial,helvetica,sans-seriff; font-size:13px; line-height:20.799999237060547px">下面是网上的其他解释,更能从本质上解释原因:</span><br style="color:rgb(75,75,75); font-family:verdana,Arial,helvetica,sans-seriff; font-size:13px; line-height:20.799999237060547px" /><span style="color:rgb(75,75,75); font-family:verdana,Arial,helvetica,sans-seriff; font-size:13px; line-height:20.799999237060547px">Iterator 是工作在一个独立的线程中,并且拥有一个 mutex 锁。 Iterator 被创建之后会建立一个指向原来对象的单链索引表,当原来的对象数量发生变化时,这个索引表的内容不会同步改变,所以当索引指针往后移动的时候就找不到要迭代的对象,所以按照 fail-fast 原则 Iterator 会马上抛出 java.util.ConcurrentModificationException 异常。</span><br style="color:rgb(75,75,75); font-family:verdana,Arial,helvetica,sans-seriff; font-size:13px; line-height:20.799999237060547px" /><p><span style="color:rgb(75,75,75); font-family:verdana,Arial,helvetica,sans-seriff; font-size:13px; line-height:20.799999237060547px">所以 Iterator 在工作的时候是不允许被迭代的对象被改变的。但你可以使用 Iterator 本身的方法 remove() 来删除对象, Iterator.remove() 方法会在删除当前迭代对象的同时维护索引的一致性。</span></p><p><span style="color:rgb(75,75,75); font-family:verdana,Arial,helvetica,sans-seriff; font-size:13px; line-height:20.799999237060547px"></span></p><p><span style="font-family:verdana,Arial,helvetica,sans-seriff; color:#4b4b4b"><span style="font-size:12.727272033691406px; line-height:20.799999237060547px">之前有一个项目从数据库中取出的对象中,有些是空的,也就是取不到。再对这些对象进行操作之前,必须的判空,这样感觉有些麻烦,那还不如直接删除list中的空对象。如何删除list的空对象呢?</span></span></p><p><span style="font-family:verdana,Arial,helvetica,sans-seriff; color:#4b4b4b"><span style="font-size:12.727272033691406px; line-height:20.799999237060547px">在java中对list进行操作很频繁,特别是进行list启遍历,这些操作我们都会,也很熟悉,但是对java中list进行删除元素,remove list中的元素就不怎么熟悉了吧,可以说很陌生,是实际操作中也很容易出错,先看看下面这个java中如何remove list 中的元素吧.</span></span></p><p><span style="font-family:verdana,Arial,helvetica,sans-seriff; color:#4b4b4b"><span style="font-size:12.727272033691406px; line-height:20.799999237060547px"></span></span></p><p><span style="font-family:verdana,Arial,helvetica,sans-seriff; color:#4b4b4b"><span style="font-size:12.727272033691406px; line-height:20.799999237060547px"></span></span></p><pre name="code" class="java" style="color: rgb(75, 75, 75); font-size: 12.727272033691406px; line-height: 20.799999237060547px;">        List<String> strList = new ArrayList<String>();
        strList.addAll(Arrays.asList("zhb","hb","xxx"));
       
        for(int i = 0; i < strList.size(); i++){
            strList.remove(i);
        }
        print(strList);//hb还存在
为什么会这样呢?

原因:List每remove掉一个元素以后,后面的元素都会向前移动,此时如果执行i=i+1,则刚刚移过来的元素没有被读取。

怎么解决?有三种方法可以解决这个问题:


1.倒过来遍历list
Java代码
for (int i = list.size()-1; i > =0; i--) {
  if (((String) list.get(i)).startsWith("abcde")) {
   list.remove(i);
  }
}
2.每移除一个元素以后再把i移回来
Java代码
for (int i = 0; i < list.size(); i++) {
  if (((String) list.get(i)).startsWith("abcde")) {
   list.remove(i);
   i=i-1;
  }
}
3.使用iterator.remove()方法删除
Java代码
for (Iterator it = list.iterator(); it.hasNext();) {
  String str = (String)it.next();
  if (str.equals("chengang")){
   it.remove();
  }
}


评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值