Java使用forEach遍历集合时删除倒数第二个元素

问题

今天在学习集合的时候说在使用迭代器遍历集合的时候不能删除集合元素否则就会抛出异常;我无意中发现删除倒数第二个元素不会抛出异常

import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;

public class ForEachTest {
    public static void main(String[] args) {
        Collection collection = new ArrayList();
        collection.add("hello");
        collection.add("15");
        collection.add("world");
        collection.add("17");
        for (Object obj : collection) {
            String str = (String)obj;
            System.out.println(str);
            if (str.equals("15")) {
                collection.remove(str);
            }
        }
        Iterator iterator = collection.iterator();
        iterator.next();
        System.out.println(collection);
    }
}

当我删除其他元素时会抛出异常:
在这里插入图片描述
但是当我删除倒数第二个元素的时候就不会抛出异常了

import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;

public class ForEachTest {
    public static void main(String[] args) {
        Collection collection = new ArrayList();
        collection.add("hello");
        collection.add("15");
        collection.add("world");
        collection.add("17");
        for (Object obj : collection) {
            String str = (String)obj;
            System.out.println(str);
            if (str.equals("world")) {
                collection.remove(str);
            }
        }
        Iterator iterator = collection.iterator();
        iterator.next();
        System.out.println(collection);
    }
}

在这里插入图片描述
而且我发现集合的最后一个元素已没有被遍历

解决

首先我们要先弄清楚forEach循环是什么?
forEach循环就是根据集合对象创建一个iterator迭代对象,用这个迭代对象来遍历集合;
每次forEach循环时都有一下两个操作:

  1. iterator.hasnext(); // 判断是否有下个元素
  2. obj = iterator.next() // 下个元素是什么,赋值给obj
public boolean hasNext() {
            return this.cursor != ArrayList.this.size;
        }

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

上面是报异常地方的源代码
我们可以看到是因为

ArrayList.this.modCount != this.expectedModCount

具体的原因是:以foreach方式遍历元素的时候,会生成iterator,然后使用iterator遍历。在生成iterator的时候,会保存一个expectedModCount参数,这个是生成iterator的时候期望集合中修改元素的次数。如果你在遍历过程中删除元素,集合中modCount就会变化

 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
    }

那为什么可以使用迭代器删除

public void remove() {
    if (lastRet < 0)
        throw new IllegalStateException();
    checkForComodification();
    
    try {
        ArrayList.this.remove(lastRet);
        cursor = lastRet;
        lastRet = -1;
        expectedModCount = modCount; // 处理expectedModCount
    } catch (IndexOutOfBoundsException ex) {
        throw new ConcurrentModificationException();
    }
}

使用迭代器删除的时候对expectModCount做了重新赋值;
如果删除的元素是倒数第二个数的话,其实是不会报错的。为什么呢,来一起看看。
之前说了foreach循环会走两个方法hasNext() 和next()。如果不想报错的话,只要不进next()方法就好啦;

public boolean hasNext() {
    return cursor != size;
}

要求hasNext()的方法返回false了,即cursor == size。其中cursor是Itr类(Iterator子类)中的一个字段,用来保存当前iterator的位置信息,从0开始。cursor本身就是游标的意思,在数据库的操作中用的比较多。只要curosr不等于size就认为存在元素。由于Itr是ArrayList的内部类,因此直接调用了ArrayList的size字段,所以这个字段的值是动态变化的,既然是动态变化的可能就会有问题出现了。

发布了35 篇原创文章 · 获赞 82 · 访问量 7525

猜你喜欢

转载自blog.csdn.net/qq_44049351/article/details/103278193