循环中使用Iterator删除元素

    最近在看阿里巴巴java开发手册,其中有一条是:不要在 foreach 循环里进行元素的 remove/add 操作。remove 元素请使用 Iterator 方式,如果并发操作,需要对 Iterator 对象加锁。

    之前自己也有了解过其中的原因,已经比较模糊,所以在这里重新看一次并记录一下。

在for中直接remove元素分析

测试代码

public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<>();
        list.add("1");
        list.add("2");
        list.add("8");
        list.add("4");
        list.add("2");
        list.add("6");
        for (String item : list){
            if ("8".equals(item)){
                list.remove(item);
            }
            System.out.print(item + "  ");
        }
    }
1  2  8  Exception in thread "main" java.util.ConcurrentModificationException
	at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:909)
	at java.util.ArrayList$Itr.next(ArrayList.java:859)
	at main.com.cjl.csdn.RemoveUseIterator.main(RemoveUseIterator.java:14)

    上面使用的是foreach的循环方式,下面我们再使用普通for循环的方式。

public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<>();
        list.add("1");
        list.add("2");
        list.add("8");
        list.add("4");
        list.add("2");
        list.add("6");
        for (int i = 0; i < list.size(); i++){
            if ("8".equals(list.get(i))){
                list.remove(list.get(i));
            }
            System.out.print(list.get(i) + "  ");
        }
    }

 

1  2  4  2  6  
Process finished with exit code 0

    由上面的两个例子我们可以猜想一下,for与foreach遍历集合时,调用方式应该是不一样的,同样是遍历为什么一个抛出异常一个没有抛出异常。所以我就对两个class文件分别进行了反编译看下他们的区别;

String item;
for(Iterator var2 = list.iterator(); var2.hasNext(); System.out.print(item + "  ")) {
    item = (String)var2.next();
    if ("8".equals(item)) {
        list.remove(item);
    }
}

    果然,上面这种是foreach的反编译结果中的一部分,而使用普通for循环方式的反编译结果与原来一致。

    

public E remove(int index) {
    rangeCheck(index);

    modCount++;
    E oldValue = elementData(index);

    int numMoved = size - index - 1;
    if (numMoved > 0)
        System.arraycopy(elementData, index+1, elementData, index,
                         numMoved);
    elementData[--size] = null; // clear to let GC do its work

    return oldValue;
}

    通过调用我们可以看到,for循环与foreach循环都是直接调用到上面的这个方法的。上面的方法中的modCount++是需要注意的,这也是为什么最后会报错的一个原因。remove之后,我们将继续遍历下一个元素,由于遍历的方式不同,所以调用的方法也是不同的。

public boolean hasNext() {
    return cursor != SubList.this.size;
}
public E next() {
    checkForComodification();
    int i = cursor;
    if (i >= SubList.this.size)
        throw new NoSuchElementException();
    Object[] elementData = ArrayList.this.elementData;
    if (offset + i >= elementData.length)
        throw new ConcurrentModificationException();
    cursor = i + 1;
    return (E) elementData[offset + (lastRet = i)];
}
final void checkForComodification() {
    if (expectedModCount != ArrayList.this.modCount)
        throw new ConcurrentModificationException();
}

    上面是foreach调用的方式,首先会去hasNext中判断是否还有下一个元素,然后去调用next方法,可以看到第一行就是去调用checkForComodification方法,该方法会去判断modCound与expectedModCount是否相等,当他们不相等的时候就会报出我们上面出现的错误。还记得在调用remove的时候我们只对modCount变量加1,并没有对expectedModCount进行改变。

    使用普通的for方式并不会走到这些方法中,所以也没有对modCount,expectedModCount进行校验。下面我们来看下使用iterator的方式。

public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<>();
        list.add("1");
        list.add("2");
        list.add("8");
        list.add("4");
        list.add("2");
        list.add("6");
        Iterator<String> iterator = list.iterator();
        while (iterator.hasNext()){
            String str = iterator.next();
            System.out.print(str + "  ");
            if ("8".equals(str)){
                iterator.remove();
            }
        }
    }

    执行上面的的代码是能够正确执行的,我们可以看到删除元素是使用的iterator中的remove方法,跟for使用的remove方法有什么不一样呢。

private class Itr implements Iterator<E>

    Itr是ArrayList是中的内部类,iterator调用的remove方式就是调用的其中的。

public void remove() {
            if (lastRet < 0)
                throw new IllegalStateException();
            checkForComodification();

            try {
                ArrayList.this.remove(lastRet);
                cursor = lastRet;
                lastRet = -1;
                expectedModCount = modCount;
            } catch (IndexOutOfBoundsException ex) {
                throw new ConcurrentModificationException();
            }
        }

可以看到其中的remove方法也会去调用到ArrayList中的方法,只是在那基础之上再封装了一层,具体内容可以看到

expectedModCount = modCount;

这句,将expectedModCount赋值与modCount一样。

在遍历下一个元素的时候会去调用到与foreach一样的方法;

public E next() {
    checkForComodification();
    int i = cursor;
    if (i >= SubList.this.size)
        throw new NoSuchElementException();
    Object[] elementData = ArrayList.this.elementData;
    if (offset + i >= elementData.length)
        throw new ConcurrentModificationException();
    cursor = i + 1;
    return (E) elementData[offset + (lastRet = i)];
}
final void checkForComodification() {
    if (expectedModCount != ArrayList.this.modCount)
        throw new ConcurrentModificationException();
}

    进行expectedModCount与modCount的判断,此时两个值是一致的,所以能够进行删除。

  总结:

    1、foreach方式的remove直接调用的ArrayList中的remove方法,修改了modCount的值,并没有修改expectedModCount的值,导致调用iterator的next方法去判断两个值时,报错。

    2、for循环方法并未涉及到比较modCount与expectedModCount的值,所以能够进行删除。

    3、iterator方式的remove调用的iterator中的remove方法,对expectedModCount也进行了一个修改,所以在调用iterator的next方法这两个值是相同的,所以能够执行成功。

  • 3
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值