集合之删除

 在阿里巴巴java开发规范中关于集合删除有一项:

11.【强制】不要在 foreach 循环里进行元素的 remove/add 操作。remove 元素请使用
Iterator 方式,如果并发操作,需要对 Iterator 对象加锁。
正例:
List<String> list = new ArrayList<>(); list.add("1");
list.add("2");
Iterator<String> iterator = list.iterator(); while (iterator.hasNext()) {
String item = iterator.next(); if (删除元素的条件) {
12/44
Java 开发手册
 iterator.remove(); }
}
反例:
for (String item : list) {
if ("1".equals(item)) {
list.remove(item); }
} 说明:以上代码的执行结果肯定会出乎大家的意料,那么试一下把“1”换成“2”,会是同样的结果吗?

我们可以debug看一下源码:

public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
    public boolean remove(Object o) {
        if (o == null) {
            for (int index = 0; index < size; index++)
                if (elementData[index] == null) {
                    fastRemove(index);
                    return true;
                }
        } else {
            for (int index = 0; index < size; index++)
                if (o.equals(elementData[index])) {
                    fastRemove(index);
                    return true;
                }
        }
        return false;
    }

    /*
     * Private remove method that skips bounds checking and does not
     * return the value removed.
     */
    private void fastRemove(int index) {
        modCount++;
        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
    }
private class Itr implements Iterator<E> {
// 游标,下个元素的下标
int cursor; // index of next element to return
// 上个元素的下标
int lastRet = -1; // index of last element returned; -1 if no such
// 期望修改的次数
int expectedModCount = modCount;

Itr() {}

/*
是否有下一个元素
*/
public boolean hasNext() {
return cursor != size;
}

/**
* 获取下一个元素
* @return
*/
@SuppressWarnings("unchecked")
public E next() {
// 检查改变
checkForComodification();
int i = cursor;
if (i >= size)
throw new NoSuchElementException();
Object[] elementData = ArrayList.this.elementData;
if (i >= elementData.length)
throw new ConcurrentModificationException();
cursor = i + 1;
return (E) elementData[lastRet = i];
}

/**
* 删除
*/
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();
}
}


/*
检查改变,判断modCount与expectedModCount是否相等
*/
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}
 }

 上面是主要涉及到的代码,当使用for循环遍历时,其实是使用Itr去遍历。

   Itr通过游标进行遍历,遍历前先调用hasNext()方法判断是否有下一个元素,有则调用next()方法将cursor置为下一个下标。

   原示例中删除是调用的ArrayList的remove()方法,删除的时候只是将mod+1。

   所以当执行到next()方法中checkForComodification()方法时mod与expectedModCount不相等抛异常:

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 com.yannic.road.Demo1.main(Demo1.java:12)

  那为什么要去执行这个checkForComodification()方法呢?因为ArrayList是线程不安全的,对ArrayList一边遍历一边删除,那么遍历的结果极有可能错误的,所以java有fast-fail机制。当mod与expectedModCount不相等时,说明正在执行删除或添加操作,既然结果最终是错的,不如早点报错抛异常。这是为了多线程设置的一种机制。

      使用iterator进行遍历删除:

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

    这里Iterator执行remove()方法是调用的Itr的remove(),再调用完ArrayList的remove()方法会将游标置为上一个下标,并且将expectedModCount = modCount,所以再执行这个checkForComodification()方法就不会抛异常了。

猜你喜欢

转载自www.cnblogs.com/ITyannic/p/12507775.html