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

您所在的位置:网站首页 for循环遍历列表题目 深入java for遍历列表(源码及操作)

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

2024-02-15 02:08| 来源: 网络整理| 查看: 265

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

List a = new ArrayList(); a.add("1"); a.add("2"); a.add("3"); for (String temp : a) { if ("1".equals(temp)) { a.remove(temp); } } //上边的for循环等价于下边这个迭代器循环 //Iterator 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 a = new ArrayList(); a.add("1"); a.add("2"); a.add("3");

2.for循环初始化

for (String temp : a)

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

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

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

//java.util.AbstractList.Itr private class Itr implements Iterator { 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



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3