集合类中 modCount 字段的作用

在阅读 AbstractList 源码时,发现有一个字段叫modCount。同时ArrayList、LinkedList、HashMap中都有

源码中的代码:

    /**
     * The number of times this list has been <i>structurally modified</i>.
     * Structural modifications are those that change the size of the
     * list, or otherwise perturb it in such a fashion that iterations in
     * progress may yield incorrect results.
     *
     * <p>This field is used by the iterator and list iterator implementation
     * returned by the {@code iterator} and {@code listIterator} methods.
     * If the value of this field changes unexpectedly, the iterator (or list
     * iterator) will throw a {@code ConcurrentModificationException} in
     * response to the {@code next}, {@code remove}, {@code previous},
     * {@code set} or {@code add} operations.  This provides
     * <i>fail-fast</i> behavior, rather than non-deterministic behavior in
     * the face of concurrent modification during iteration.
     *
     * <p><b>Use of this field by subclasses is optional.</b> If a subclass
     * wishes to provide fail-fast iterators (and list iterators), then it
     * merely has to increment this field in its {@code add(int, E)} and
     * {@code remove(int)} methods (and any other methods that it overrides
     * that result in structural modifications to the list).  A single call to
     * {@code add(int, E)} or {@code remove(int)} must add no more than
     * one to this field, or the iterators (and list iterators) will throw
     * bogus {@code ConcurrentModificationExceptions}.  If an implementation
     * does not wish to provide fail-fast iterators, this field may be
     * ignored.
     */
    protected transient int modCount = 0;

先看注释:

该字段表示list结构上被修改的次数。结构上的修改指的是那些改变了list的长度大小或者使得遍历过程中产生不正确的结果的其它方式。

该字段被Iterator以及ListIterator的实现类所使用,如果该值被意外更改,Iterator或者ListIterator 将抛出ConcurrentModificationException异常,

这是jdk在面对迭代遍历的时候为了避免不确定性而采取的快速失败原则。

子类对此字段的使用是可选的,如果子类希望支持快速失败,只需要覆盖该字段相关的所有方法即可。单线程调用不能添加删除terator正在遍历的对象,

否则将可能抛出ConcurrentModificationException异常,如果子类不希望支持快速失败,该字段可以直接忽略。

例子代码:

public static void main(String[] args) {
	 List<String> list = new ArrayList<>();
	    list.add("a");
	    list.add("b");
	    list.add("c");
	    list.add("d");
	    list.add("e");
	    Iterator<String> iterator = list.iterator();
	    int i = 0;
	    while(iterator.hasNext()){
	        if(i==3){
	        	iterator.remove();
	        	//list.remove(it.next()); 会报异常
	        }
	        System.out.println("第"+i+"个元素"+iterator.next());
	        i++ ;
	    }
	    System.out.println("----------------");
	    Iterator<String> iterator2 = list.iterator();
	    while(iterator2.hasNext()){
	        System.out.println(iterator2.next());
	    }
}

如果用list.remove(it.next());会报ConcurrentModificationException异常,因为注释明确指出:结构上的修改指的是那些改变了list的长度大小或者使得遍历过程中产生不正确的结果的其它方式。

另:注意it.remove()删除的是最近的一次it.next()获取的元素,而不是当前iterator中游标指向的元素!!

因此,本例中i==3时,删除的其实是c,而不是d,这很容易被忽视或者误解。如果想删掉d,正确操作是先调用it.next()获取到具体元素,再判断;而且由于调用了it.next(),此时游标已经指向我们期望删除的值了。想直接数数字进行删除,在这里会容易出错误。

其实我们可以查看Iterator的源码来验证:

public Iterator<E> iterator() {
    return new Itr();
}

返回的是一个Itr对象,这个Itr是ArrayList的内部类

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;  //修改计数器期望值
 
    public boolean hasNext() {
        return cursor != size;
    }
    @SuppressWarnings("unchecked")
    public E next() {
        checkForComodification();
        int i = cursor; //此时的游标,指向的是本次要遍历的对象,因为上一次已经++了,初始值为0,没有++的情况下是第一个元素
        if (i >= size)   //越界了
            throw new NoSuchElementException();
        Object[] elementData = ArrayList.this.elementData;
        if (i >= elementData.length)
            throw new ConcurrentModificationException();
        cursor = i + 1;  //游标指向了下一个元素, 但 i 的值没有变
        return (E) elementData[lastRet = i];   //将 i 赋值给lastRet,取的值是方法开始时int i=cursor;中的cursor指向的值,而且最终这个游标的数值赋值给了lastRet
    }
    public void remove() {
        if (lastRet < 0)  // 如果没有next()操作就直接remove的话,lastRet=-1,会抛异常
            throw new IllegalStateException();
        checkForComodification();
        try {
            ArrayList.this.remove(lastRet); // remove之前,cursor、lastRet的值没有修改,都是上次next之后的值,因此此处的lastRet指向上次next获取的元素
            cursor = lastRet;
            lastRet = -1;
            expectedModCount = modCount;  // 手动将ArrayList.remove()后modCount的值赋给expectedModCount,避免引起不一致
        } catch (IndexOutOfBoundsException ex) {
            throw new ConcurrentModificationException();
        }
    }
}

以上代码告诉我们,iterator.remove()实际是remove了上次next返回的元素,并且为了防止ConcurrentModificationException异常,手动修复了修改计数的期望值,而且如果没有经过next操作就直接remove的话,会因为初始的lastRet=-1而抛出IllegalStateException异常。

猜你喜欢

转载自blog.csdn.net/weixin_40841731/article/details/84873527