Java遍历中的删除操作
目录
前言
第一种:普通for循环
第二种:利用迭代器
总而言之
前言
在遍历Collection的过程中删去部分元素的情景是非常常见的,但是利用Java编写这样的程序,常常会出现一些让人很费解的问题。本文将阐释这些问题的产生原因以及解决办法。
不妨以List作为例子,当我们需要对一个字符串数组删去其中包含"6."前缀的元素时,我们下意识会编写出这样的代码:
-
第一种:普通for循环
import java.util.ArrayList;import java.util.Iterator;import java.util.List;public class iterator { public static void main(String[] args) { List list = new ArrayList(); list.add("6.045"); list.add("6.005"); list.add("6.813"); System.out.println("删除前:" + list); for(int i=0;i<list.size();i++) { String temp = list.get(i); if(temp.startsWith("6.")) //删去其中包含"6."前缀的元素 //list.remove(i); list.remove(temp); //两种方法效果都一样 } System.out.println("删除后:" + list); }}
这样的写法我们在C/C++里经常用,无非是多使用了Java自带的List.remove()的方法删去元素罢了。我们很当然的觉得程序运行的结果一定是:list中所有元素都被删除,然而....
居然有一个元素没有被删除!这是为什么??
最开始时,i=0,list.size()=3,temp=list[0]=6.045,包含"6."前缀,删去,i++;删除后,list中剩余所有元素都向前移了一位(索引-1:类似于取出队首元素,其他元素自动前进补齐空缺),此时i=1,list.size()=2,temp=list[0]=6.813,包含"6."前缀,删去,i++;此后list.size()=1,循环结束
所以,其实问题出在了List.remove()方法使用后,元素的索引改变导致某些元素未被访问到,更改方式其实很简单——倒序遍历数组
import java.util.ArrayList;import java.util.Iterator;import java.util.List;public class iterator { public static void main(String[] args) { List list = new ArrayList(); list.add("6.045"); list.add("6.005"); list.add("6.813"); System.out.println("删除前:" + list); for(int i = list.size() - 1; i >= 0; i--) { String temp = list.get(i); if(temp.startsWith("6.")) list.remove(i); //list.remove(temp); } System.out.println("删除后:" + list); }}
好耶,和预想的结果相同!!
-
第二种:利用迭代器
如果是接触过迭代器的小伙伴,也可能写出这样的代码
import java.util.ArrayList;import java.util.Iterator;import java.util.List;public class iterator { public static void main(String[] args) { List list = new ArrayList(); list.add("6.045"); list.add("6.005"); list.add("6.813"); System.out.println("删除前:" + list); Iterator ite = list.iterator(); //迭代器 while (ite.hasNext()) { String next = ite.next(); if(next.startsWith("6.")) list.remove(next); } System.out.println("删除后:" + list); }}
或者引入写法比较简单的增强for循环【实际就是简化版的迭代器,底层实现相同】
import java.util.ArrayList;import java.util.Iterator;import java.util.List;public class iterator { public static void main(String[] args) { List list = new ArrayList(); list.add("6.045"); list.add("6.005"); list.add("6.813"); System.out.println("删除前:" + list); for(String s:list) { if(s.startsWith("6.")) list.remove(s); } System.out.println("删除后:" + list); }}
这样的写法真是方便又高级,但是怎么会报错啊...
运行结果中抛出java.util.ConcurrentModificationException
异常信息。这是因为触发了集合中并发修改的异常,接下来我们通过源码对抛出异常的原因进行剖析。
public Iterator iterator() { return new Itr(); }
在ArrayList
集合的Iterator
方法中,是通过返回Itr
对象来获得迭代器的。Itr
是ArrayList
的一个内部类,它实现了Iterator
接口,代码如下:
private class Itr implements Iterator { int cursor;// index of next element to return int lastRet = -1; // index of last element returned; -1 if no such int expectedModCount = modCount; Itr() {} 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(); } }
ModCount定义在AbstractList接口中,初始值为0,定义如下:
protected transient int modCount = 0;
ModCount是版本号,在对集合进行变更操作(增加、删除、修改等)的时候会对版本号进行 +1 操作。
结合上述代码进行抛出 java.util.ConcurrentModificationException 异常的解释。
①初始化ArrayList,添加三次元素,即三次调用add()方法,进行三次 modCount++ ; 此时,modCount = 3 , size = 3 ;
②初始化Iterator迭代器进行循环,此时,expectedModCount = modCount = 3 ,cursor=0,lastRet=−1
③进行hasNext判断,cursor != size;成立,进入循环
④调用next()方法,首先进行checkForComodification()校验,modCount==expectedModCount,校验通过,返回值,此时lastRet=0;cursor=1
final void checkForComodification() { if (modCount != expectedModCount) throw new ConcurrentModificationException(); }
⑤调用集合
remove()
方法,modCount++;
此时modCount = 4 ; size = 2⑥再次调用hasNext()方法判断,
cursor != size;
成立,进入循环⑦调用next()方法进行校验,modCount ! = expectedModCount ,校验未通过,抛出java.util.ConcurrentModificationException异常
总结:
修改方法也很简单,使用迭代器自带的remove方法即可
import java.util.ArrayList;import java.util.Iterator;import java.util.List;public class iterator { public static void main(String[] args) { List list = new ArrayList(); list.add("6.045"); list.add("6.005"); list.add("6.813"); System.out.println("删除前:" + list); Iterator ite = list.iterator(); while (ite.hasNext()) { String next = ite.next(); if(next.startsWith("6.")) ite.remove(); //迭代器remove方法 } System.out.println("删除后:" + list); }}
可以看见,结果与预期相同,终于解决所有问题啦!
总而言之
可行的办法分为两类:(1)普通for循环+倒序遍历+List.remove()
(2)迭代器(非增强for循环)+迭代器remove()
参考文章
Java迭代器详解,看这一篇就够了_WYSCODER的博客-CSDN博客_java迭代器Java迭代器详解,看这一篇就够了!https://blog.csdn.net/sheng0113/article/details/122712947