Collection与Iterator的remove()方法区别与ConcurrentModificationException异常

在我的上一篇文章 Java中三种遍历Collection中元素的方法(Iterator、forEach、for循环)对比 中提到Iterator和forEach循环在遍历Collection中元素时最大的差别就是在方法remove()上,由于在Iterator的remove()方法中维护一个标志位,所以删除元素时不会出现异常,所以本篇文章就深入Collection与Iterator的源码看看内部究竟是如何实现的。

一. Collection及其实现类ArrayList的部分源码

1.Collection内部源码

首先我们来看一下Collection内部源码(为方便分析,此处只展示与本篇文章有关的部分):

public interface Collection<E> extends Iterable<E> {

    boolean remove(Object o);

    Iterator<E> iterator();

    /**
     *  此处省去其他方法定义
     */
}

可以看到Collection是一个接口,内部定义了remove()iterator()方法。

2.ArrayList内部源码

由于Collection接口内部无具体实现,所以我们来看Collection的一个最常用的实现类ArrayList内部源码(为方便分析,此处只展示与本篇文章有关的部分):

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 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
    }


    /* -----------------------我是便于观察的分割线----------------------- */


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

    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;
            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();
            }
        }

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

    /**
     *  此处省去其他方法定义
     */
}

在ArrayList中并没有直接实现Collection接口,而是通过继承AbstractList抽象类,而AbstractList抽象类又继承了AbstractCollection抽象类,最终AbstractCollection抽象类实现Collection接口;所以ArrayList间接实现了Collection接口,有兴趣的大佬可以自己去研究下为什么这样子设计,在这里就不多加讨论。

可以看到在ArrayList中有实现remove()iterator()方法,并且通过iterator()方法得到的内部类Itr实现了Iterator接口,在Itr内部类中也有实现remove()方法,下面就来具体的探讨其中的区别。

二. ArrayList的remove()方法分析

1.remove()方法

在ArrayList的remove()方法内部的实现主要是通过循环找到元素的下标, 然后调用私有的fastRemove()方法:

    fastRemove(index);

remove()方法没啥好讲的,关键在于调用的fastRemove()方法上。

2.fastRemove()方法

fastRemove()方法中会先修改modCount的值,然后将通过复制一个新的数组的方法将原来index位置上的值覆盖掉,最后数组大小减一。我们重点关注fastRemove()方法的第一行代码:

    modCount++;

也就是每次调用remove()方法都会使modCount的值加一。那么modCount变量又是什么呢?

3.modCount变量

modCount在ArrayList中没有定义,是在ArrayList的父类AbstractList抽象类中定义的:

public abstract class AbstractList<E> extends AbstractCollection<E> implements List<E> {

    protected transient int modCount = 0;

    /**
     *  此处省去其他方法定义
     */
}

modCount的作用是记录操作(添加删除)ArrayList中元素的次数(这个很关键),每次操作ArrayList中元素后就会使modCount加一。

三. Iterator的remove()方法分析

看源码可知ArrayList通过iterator()方法得到了一个内部类Itr,这个内部类实现了Iterator接口,我们重点分析内部类Itr中的实现。

1.expectedModCount 变量

在内部类Itr中定义了一个变量expectedModCount :

    int expectedModCount = modCount;

expectedModCount 只在new一个Itr对象时初始化为modCount

2.next()与remove()方法

在调用Itr对象的next()remove()方法时第一步会先调用checkForComodification()方法。

    checkForComodification();

并且在remove()方法中会调用ArrayList.this.remove(lastRet)方法(也就是具体的ArrayList对象的remove()方法,上面我们讲过,在ArrayList对象的remove()方法中会使得modCount的值加一),然后修改expectedModCount 的值为modCount

3.checkForComodification()方法

checkForComodification()会检查expectedModCount与modCount 是否相等,如果不相等就会抛出ConcurrentModificationException异常。

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

四. 总结

通过上面的分析我们可以得出,Collection与Iterator的remove()方法最大的区别就是:
Iterator的remove()方法会在删除元素后将modCount 的值赋值给expectedModCount,使其又相等

1.如果我们在Iterator循环中调用Collection的remove()方法

public static void display(Collection<Object> collection) {
    Iterator<Object> it = collection.iterator();

    // 会抛出ConcurrentModificationException异常
    while(it.hasNext()) {
        Object obj = it.next();
        collection.remove(obj ); 
    }
}

由于collection.remove(obj )只会删除obj元素后将modCount 的值加一,并不会修改expectedModCount的值,所以当下一次调用it.next()方法时发现modCount != expectedModCount,将抛出ConcurrentModificationException异常。

2.如果我们在Iterator循环中调用Iterator的remove()方法

public static void display(Collection<Object> collection) {
    Iterator<Object> it = collection.iterator();
    // 正常执行
    while(it.hasNext()) {
        Object obj = it.next();
        it.remove(obj ); 
    }
}

由于it.remove(obj )会在删除obj元素后将modCount 的值加一,并将expectedModCount重新赋值为modCount ,使其相等,所以当下一次调用it.next()方法时发现modCount == expectedModCount,正常执行。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 200,045评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,114评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 147,120评论 0 332
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,902评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,828评论 5 360
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,132评论 1 277
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,590评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,258评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,408评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,335评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,385评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,068评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,660评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,747评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,967评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,406评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,970评论 2 341

推荐阅读更多精彩内容