目录
前言
第一种:普通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<String> 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<String> 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<String> list = new ArrayList<>();
list.add("6.045");
list.add("6.005");
list.add("6.813");
System.out.println("删除前:" + list);
Iterator<String> 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<String> 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<E> iterator() {
return new Itr();
}
在ArrayList
集合的Iterator
方法中,是通过返回Itr
对象来获得迭代器的。Itr
是ArrayList
的一个内部类,它实现了Iterator
接口,代码如下:
private class Itr implements Iterator<E> {
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
总结:
修改方法也很简单,使用迭代器自带的remove方法即可
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
public class iterator {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("6.045");
list.add("6.005");
list.add("6.813");
System.out.println("删除前:" + list);
Iterator<String> 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()