浅谈 Java 加强For

2019-01-23  本文已影响0人  鸡蛋灌烧饼

一、问题引入

增强for遍历集合时,remove一个数据,竟然抛出了异常(ConcurrentModificationException),心中万马奔腾,这是怎么回事?


二、List 常用遍历方法

//普通for
for (int i = 0; i < list.size(); i++) {
    ...;
}

//加强for
for (String s : list) {
    ...;
}

//迭代器
Iterator<String> iterable = list.iterator();
while (iterable.hasNext()){
    String s = iterable.next();
}
这里主要研究下加强for特点,下面写一个加强for并查看编译后的样子: 加强for和编译后文件

其实编译之后发现变成了 for + iterator由此发现以下几点

  • 简单的书写风格还是要编译成迭代器的
  • 底层使用的是迭代器实现的,每次循环都通过迭代器的hasNext()方法判断是否有下一项,通过迭代器的next取值,并赋值给临时变量的
  • 如果随意在for中执行list的add或者remove方法的话会引起崩溃(ConcurrentModificationException),实际上使用迭代器自带的删除就没什么问题了

三、看源码

查看ArrayList#ItrAPI
源码如下

private class Itr implements Iterator<E> {
    int cursor;
    int lastRet = -1;
    int expectedModCount;

    Itr() {
        this.expectedModCount = ArrayList.this.modCount;
    }

    public boolean hasNext() {
        return this.cursor != ArrayList.this.size;
    }
    //获取下一个元素
    public E next() {
        this.checkForComodification();
        int var1 = this.cursor;
        if (var1 >= ArrayList.this.size) {
            throw new NoSuchElementException();
        } else {
            Object[] var2 = ArrayList.this.elementData;
            if (var1 >= var2.length) {
                throw new ConcurrentModificationException();
            } else {
                this.cursor = var1 + 1;
                return var2[this.lastRet = var1];
            }
        }
    }

    public void remove() {
        if (this.lastRet < 0) {
            throw new IllegalStateException();
        } else {
            this.checkForComodification();

            try {
                //迭代器调List的删除方法
                ArrayList.this.remove(this.lastRet);
                this.cursor = this.lastRet;
                this.lastRet = -1;
                //同步迭代器本身的标识
                this.expectedModCount = ArrayList.this.modCount;
            } catch (IndexOutOfBoundsException var2) {
                throw new ConcurrentModificationException();
            }
        }
    }

    public void forEachRemaining(Consumer<? super E> var1) {
        Objects.requireNonNull(var1);
        int var2 = ArrayList.this.size;
        int var3 = this.cursor;
        if (var3 < var2) {
            Object[] var4 = ArrayList.this.elementData;
            if (var3 >= var4.length) {
                throw new ConcurrentModificationException();
            } else {
                while(var3 != var2 && ArrayList.this.modCount == this.expectedModCount) {
                    var1.accept(var4[var3++]);
                }

                this.cursor = var3;
                this.lastRet = var3 - 1;
                this.checkForComodification();
            }
        }
    }

    /**此方法会比较ArrayList的标识和迭代其中是否相同,
   *不相同直接抛出异常,迭代器的方法都会调用这个方法
   */
    final void checkForComodification() {
        if (ArrayList.this.modCount != this.expectedModCount) {
            throw new ConcurrentModificationException();
        }
    }
}

四、问题原因解析

先了解一下List中有一个int modCount作为标识记录集合的修改次数,每次remove,add等都会累加1,所以:

  • 迭代循环中,直接修改List结构:在开始迭代时,会把当前的标识赋值到迭代器int expectedModCount,在迭代过程中,对List结构修改List的标识未同步到迭代器中,所以在执行遍历的时候会抛出异常。
  • 而使用迭代器中的删除则不一样,迭代器中删除之后,会修改List的标识,然后同步迭代器本身的标识,所以不会抛出异常。
  • PS :modCount 是记录这个列表在结构上被修改的次数。
  • 集合的一种错误机制fail-fast 了解一下.
上一篇 下一篇

猜你喜欢

热点阅读