您的位置:首页 > 其它

ArrayList 迭代器学习笔记

2017-05-26 12:45 441 查看
我们先来看一段代码:

List<String> list = new ArrayList<>();
list.add("str1");
list.add("str2");
list.add("str3");
for (String s : list) {
if ("str1".equals(s)) {
list.remove(s);
}
}


这段代码看起来好像没有什么问题,但是如果我们运行,就会抛出ConcurrentModificationException异常。

其实这不是特例,每当我们使用迭代器遍历元素时,如果修改了元素内容(添加、删除元素),就会抛出异常,由于 foreach 同样使用的是迭代器,所以也有同样的情况,大家可以自行试一下。我们来通过源码探究一下这个现象的根本原因。

ArrayList 源码阅读

下面是 ArrayList 的部分源码,可以明显的看到共有两个
remove()
方法,一个属于 ArrayList 本身,还有一个属于其内部类 Itr。

public class ArrayList<E> {
​
void remove() {
modCount++;  // 继承自AbstractList的属性,保存对其中元素的修改次数,每次增加或删除时加1
// 具体删除操作代码
//...
}

public Iterator<E> iterator() {
return new Itr();
}
​
private class Itr implements Iterator<E> {
int cursor;       // index of next element to return
// 在创建迭代器时将当前ArrayList的修改次数赋值给 expectedModCount 保存
int expectedModCount = modCount;
​
public boolean hasNext() {
return cursor != size;
}
​
@SuppressWarnings("unchecked")
public E next() {
// 检查当前所在的 ArrayList 的 modCount 是否与创建 Itr 时的值一致,
// 也就是判断获取了Itr迭代器后 ArrayList 中的元素是否被 Itr 外部的方法改变过。
checkForComodification();
// 具体的获取下一个元素的代码
// ...
}
​
public void remove() {
if (lastRet < 0)
throw new IllegalStateException();
// 同 next 中的 checkForComodification 方法
checkForComodification();
​
try {
ArrayList.this.remove(lastRet);
cursor = lastRet;
lastRet = -1;
// Itr 内部的删除元素操作,会更新 expectedModCount 值,而外部的则不会
expectedModCount = modCount;
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
​
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}
}


ArrayList 类中有一个 modCount 属性,这个属性是继承子AbstractList,其保存了我们对 ArrayList 进行的的操作次数,当我们添加或者删除元素时,modeCount 都会进行对应次数的增加。

迭代器迭代时外部方法添加或删除元素

在我们使用 ArrayLis 的
iterator()
方法获取到迭代器进行遍历时,会把 ArrayList 当前状态下的 modCount 赋值给 Itr 类的 expectedModeCount 属性。如果我们在迭代过程中,使用了 ArrayList 的
remove()
add()
方法,这时 modCount 就会加 1 ,但是迭代器中的expectedModeCount 并没有变化,当我们再使用迭代器的
next()
方法时,它会调用
checkForComodification()
方法,即

final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}


发现现在的 modCount 已经与 expectedModCount 不一致了,则会抛出
ConcurrentModificationException
异常。

迭代器迭代时,使用迭代器的方法添加或删除元素

但是如果我们使用迭代器提供的
remove()
方法,由于其有一个操作:
expectedModCount = modCount;
,会修改expectedModCount 的值,所以就不会存在上述问题。

总结

当我们使用迭代器迭代对象的时候,不要使用迭代器之外的方法修改元素,否则会报异常。如果我们要在迭代器迭代时进行修改,可以使用迭代器提供的删除等方法。或者使用其他方法遍历修改。

注意:

特殊情况下在迭代器过程中使用 ArrayList 的删除方法不会报异常,就是 只删除倒数第二个元素的时候,代码如下:

List<String> list = new ArrayList<>();
list.add("str1");
list.add("str2");
list.add("str3");
for (String s : list) {
if ("str2".equals(s)) { // 必须只能是倒数第二个元素,这样才不会抛异常
list.remove(s);
}
}


其原因是迭代器的
hasNext()
方法:

public boolean hasNext() {
return cursor != size;
}


在只删除了倒数第二个元素的时候,cursor 会与 size 相等,这样
hasNext()
方法会返回 false ,结束迭代,也就不会进入
next()
方法中,进而执行
checkForComodification()
检查方法抛出异常。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: