深入java for遍历列表(源码及操作)

for循环是Iterator的简化操作方式,可以方便地对一个数组或列表进行遍历。
今天遇到一个小tip,就是在for循环中不能对列表进行“结构性变动操作”,什么是结构变动操作呢?简单来说就是remove、add这样对原有列表元素进行增删的操作。
举个例子:

List<String> a = new ArrayList<String>();
a.add("1");
a.add("2");
a.add("3");
for (String temp : a) {
    if ("1".equals(temp)) {
        a.remove(temp);
    }
}
//上边的for循环等价于下边这个迭代器循环
//Iterator<String> iterator = a.iterator();
//while(iterator.hasNext()){
//  String s = iterator.next();
//  if("1".equals(s))
//      a.remove(s);
//}

这段代码想要实现遍历找到值为“3”的元素,并删除该元素
看似没什么问题,但在运行时会抛出java.util.ConcurrentModificationException异常。

Exception in thread "main" java.util.ConcurrentModificationException
    at java.util.ArrayList$Itr.checkForComodification(Unknown Source)
    at java.util.ArrayList$Itr.next(Unknown Source)
    at com.leif.action.Test.main(Test.java:14)

这是因为在for循环中不支持对列表进行增/删的操作。
我们从头运行调试,看一下源码是怎么处理的:
1.初始化
这个不用多说了

List<String> a = new ArrayList<String>();
a.add("1");
a.add("2");
a.add("3");

2.for循环初始化

for (String temp : a) 

首次进入for循环会初始化生成迭代器(没错,for循环其实是迭代器遍历的简化操作),源码如下:

//java.util.AbstractList
    public Iterator<E> iterator() {
        return new Itr();
    }

这里的Itr()是ArrayList父类AbstractList的一个内部类,用来实现遍历操作。

//java.util.AbstractList.Itr
    private class Itr implements Iterator<E> {
        int cursor;       // 下一个遍历到的元素的下标
        int lastRet = -1; // 上一个遍历到的元素的下标
        int expectedModCount = modCount;

这里是初始化执行的命令。

cursor: 下一个遍历到的元素的下标,目前还没有开始遍历,所以cursor是0

lastRet:上一次操作的元素的下标,初始值为-1

expectedModCount 和 modCount:这两个就是对队列有结构性操作的计数器,具体作用我们下边看到,对列表有remove或者add操作都会在这两个计数器上体现出来。
比如现在modCount的值就是3,因为我们在初始化列表的时候进行了三次add操作

3.for循环遍历
初始化完迭代器后,开始迭代器的遍历操作:

//java.util.AbstractList.Itr.hasNext()
 public boolean hasNext() {
            return cursor != size;//0!=3
        }

注意,这里执行的hasNext是ArrayList的内部类Itr的hasNext()方法。

cursor: 下一个遍历到的元素的下标

size:列表的大小,这里是3

cursor!=size说明还没遍历到列表的结尾,就会调用next()方法来获取元素

//java.util.AbstractList.Itr.next()
public E next() {
       checkForComodification();//检查是否有进行增删操作,见下方源码
       int i = cursor;
       if (i >= size)//cursor越界
          throw new NoSuchElementException();
       Object[] elementData = ArrayList.this.elementData;//获取列表数组
       if (i >= elementData.length)//如果将要遍历的下标大于等于列表的大小,则说明之前对列表有过增删操作,否则i也就是cursor是肯定不会大于等于length的
          throw new ConcurrentModificationException();
       cursor = i + 1;//cursor+1,移向下一元素
       return (E) elementData[lastRet = i];//返回当前元素,并把lastRet指向当前元素
}

//java.util.AbstractList.Itr.checkForComodification()
final void checkForComodification() {
       if (modCount != expectedModCount)//如果两者不相同,则说明有对列表进行过增删操作
           throw new ConcurrentModificationException();
}

4.删除元素
temp会指向返回对象的地址,这时候进行删除操作,调用的是ArrayList.remove()方法,我们来看看怎么运行的。

//main
for (String temp : a) {
    if ("1".equals(temp)) {
        a.remove(temp);
    }
}
//java.util.ArrayList.remove(Object o)
    public boolean remove(Object o) {
        if (o == null) {//是否要删除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);//删除操作,见下,此时index为0
                    return true;
                }
        }
        return false;
    }
//java.util.ArrayList.fastRemove(int index)
private void fastRemove(int index) {
        modCount++;//进行了结构性变化的删除操作,modCount自增,注意,此时modCount就会和expectedModCount不同,下次遍历的时候就会抛出异常
        int numMoved = size - index - 1;//numMoved就是删除后要移动的元素,size=3,index=0
        if (numMoved > 0)
            System.arraycopy(elementData, index+1,elementData, index,numMoved);
        elementData[--size] = null; // clear to let GC do its work
    }

5.继续遍历
此时删除了那个元素,继续遍历

//java.util.AbstractList.Itr.hasNext()
 public boolean hasNext() {
            return cursor != size;//1!=2
        }

cursor!=size说明还没遍历到列表的结尾,就会调用next()方法来获取元素

//java.util.AbstractList.Itr.next()
public E next() {
       checkForComodification();//异常会在此抛出,见下
       int i = cursor;
       if (i >= size)//cursor越界
          throw new NoSuchElementException();
       Object[] elementData = ArrayList.this.elementData;//获取列表数组
       if (i >= elementData.length)//如果将要遍历的下标大于等于列表的大小,则说明之前对列表有过增删操作,否则i也就是cursor是肯定不会大于等于length的
          throw new ConcurrentModificationException();
       cursor = i + 1;//cursor+1,移向下一元素
       return (E) elementData[lastRet = i];//返回当前元素,并把lastRet指向当前元素
}

//java.util.AbstractList.Itr.checkForComodification()
final void checkForComodification() {
       if (modCount != expectedModCount)//4!=3 抛出异常!!
           throw new ConcurrentModificationException();
}

至此,我们已经看完了从初始化到抛出异常的整个流程,可见该异常是ArrayList开发者有意而为之的,目的就是不想让使用者在遍历的时候进行删除(或添加)操作。
原因我认为可能就是因为这样操作是非线程安全的吧,如果多个线程同时对同一个ArrayList进行遍历,其中一个进行了增删操作的话,那么另外一个线程在遍历的时候就会出问题。

那么怎么实现在遍历的时候进行元素删除操作呢?
可以参见下方链接的博客。
https://www.cnblogs.com/andy-zhou/p/5339683.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值