Java用Iterator遍历集合以及如何保证安全性的原理
Java中遍历集合的方式
以list集合为例,有三种遍历方式。
List<String> list=new ArrayList<>();
list.add("2");
list.add("2");
list.add("2");
方式1,增强for循环
for (String s : list) {
if("2".equals(s)){
list.remove(s);
}
}
方式2,for循环
for(int i=0;i<list.size();i++){
String s=list.get(i);
if("2".equals(s)){
list.remove(s);
}
}
方式3,iterator遍历器
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()){
String next = iterator.next();
if("2".equals(next)){
iterator.remove();
}
}
集合遍历过程中删除元素时的安全问题
首先上述三种遍历集合的方式,只有方式3能够安全的删除满足条件的元素。
第一种方式会报一个java.util.ConcurrentModificationException异常,其实增强for循环在遍历元素的时候底层是用迭代器实现的,虽然用迭代器遍历时删除元素是安全的,但是,代码中删除元素还是用的集合原本的remove方法。以至于为什么会报错,下面的文章再讨论。
第二种遍历方式,虽然不会报错,但是却没有完善删除满足条件的元素,由于ArrayList底层使用数组方式实现,当删除其中某一元素时,其余数组下标会前移,导致继续进行删除时会略过下一元素,最终的结果也会异常。
只有第三种方式会正常删除所有满足条件的元素。
Iterator删除元素是如何保证安全的
以ArrayList为例,在获取iterator对象时,底层是创建了一个Itr对象。
public Iterator<E> iterator() {
return new Itr();
}
Itr主要的属性和方法如下:
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();
}
}
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}
注意到,Itr 中有个 expectedModCount 属性初始化和 ArrayList 的 modCount 属性相等。
modCount属性是做什么用的呢?
从 ArrayList 源码中可以看出,函数中在每次执行 remove,add,clear 方法时,都会对 modCount 加一,其实他就相当于一个记录 ArrayList 版本的变量,每对他进行操作时就会将其加一,表示进行了新的操作。
那么回到 Iterator 中的 expectedModCount,初始化也就对应当前集合的版本。在调用 Iterator 的 next 方法或者 remove 方法时,在实际删除元素之前先进行版本校验(checkForComodification方法),如果版本不一致则会抛出一个异常。就比如在遍历器遍历的时候,又对原集合进行了添加或删除元素的操作,则会导致异常。在实际删除元素之后 Iterator 也会同步一下 ArrayList 的 modCount。因为 Iterator 中 remove 方法删除元素的时候也是调用 ArrayList 的 remove 方法。
其实就是因为在执行Iterator的一些方法之前要对原集合的版本进行校验,所以才能保证操作的安全。