问:简单说说 Iterator 和 ListIterator 的区别?
答:区别主要如下。
(1)ListIterator 有 add() 方法,可以向 List 中添加对象,而 Iterator 不能。
(2)ListIterator 和 Iterator 都有 hasNext() 和 next() 方法,可以实现顺序向后遍历,但是 ListIterator 有 hasPrevious() 和 previous() 方法,可以实现逆向(顺序向前)遍历,Iterator 就不可以。
(3)ListIterator 可以定位当前的索引位置,通过 nextIndex() 和 previousIndex() 可以实现,Iterator 没有此功能。
(4)都可实现删除对象,但是 ListIterator 可以实现对象的修改,通过 set() 方法可以实现,Iterator 仅能遍历,不能修改。
(5)ListIterator 是 Iterator 的子接口。
注意:容器类提供的迭代器都会在迭代中间进行结构性变化检测,如果容器发生了结构性变化,就会抛出 ConcurrentModificationException,所以不能在迭代中间直接调用容器类提供的 add、remove 方法,如需添加和删除,应调用迭代器的相关方法。
问:为什么使用 for-each 时调用 List 的 remove 方法元素会抛出 ConcurrentModificationException 异常?
答:首先 Java 提供了一个 Iterable 接口返回一个迭代器,常用的 Collection、List、Set 等都实现了这个接口,该接口的 iterator() 方法返回一个标准的 Iterator 实现,实现 Iterable 接口允许对象成为 for-each 语句的目标来遍历底层集合序列,因此使用 for-each 方式遍历列表在编译后实质是迭代器的形式实现。之所以会出现 ConcurrentModificationException 异常我们需要去看下最常见的 ArrayList 中 iterator() 方法的实现(别的集合 iterator 类似),如下:
private class Itr implements Iterator {
protected int limit = ArrayList.this.size; //集合列表的个数尺寸
int cursor; //下一个元素的索引位置
int lastRet = -1; //上一个元素的索引位置
int expectedModCount = modCount;
public boolean hasNext() {
return cursor < limit;
}
@SuppressWarnings("unchecked")
public E next() {
//modCount用于记录ArrayList集合的修改次数,初始化为0,
// 每当集合被修改一次(结构上面的修改,内部update不算),
// 如add、remove等方法,modCount + 1,所以如果modCount不变,
// 则表示集合内容没有被修改。
if (modCount != expectedModCount) throw new ConcurrentModificationException();
int i = cursor; //如果下一个元素的索引位置超过了集合长度抛出异常
if (i >= limit) throw new NoSuchElementException();
Object[] elementData = ArrayList.this.elementData;
if (i >= elementData.length)
throw new ConcurrentModificationException(); //调用一次cursor加一次
cursor = i + 1;
//返回当前一个元素
return (E) elementData[lastRet = i];
}
public void remove() {
//lastRet每次在remove成功后都需要在next()中重新赋值,
// 否则调用一次后再调用为-1异常,因此使用迭代器的remove方法
// 前必须先调用next()方法。
if (lastRet < 0) throw new IllegalStateException();
if (modCount != expectedModCount) throw new ConcurrentModificationException();
try {
ArrayList.this.remove(lastRet);
cursor = lastRet;
lastRet = -1;
expectedModCount = modCount;
limit--;
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
......
}
通过上面的源码发现迭代操作中都有判断 modCount != expectedModCount 的操作,在 ArrayList 中 modCount 是当前集合的版本号,每次修改(增、删)集合都会加 1,expectedModCount 是当前迭代器的版本号,在迭代器实例化时初始化为 modCount,所以当调用 ArrayList.add() 或 ArrayList.remove() 时只是更新了 modCount 的状态,而迭代器中的 expectedModCount 未修改,因此才会导致再次调用 Iterator.next() 方法时抛出 ConcurrentModificationException 异常。而使用 Iterator.remove() 方法没有问题是因为 Iterator 的 remove() 方法中有同步 expectedModCount 值,所以当下次再调用 next() 时检查不会抛出异常。这其实是一种快速失败机制,机制的规则就是当多个线程对 Collection 进行操作时若其中某一个线程通过 Iterator 遍历集合时该集合的内容被其他线程所改变,则抛出 ConcurrentModificationException 异常。
因此在使用 Iterator 遍历操作集合时应该保证在遍历集合的过程中不会对集合产生结构上的修改,如果在遍历过程中需要修改集合元素则一定要使用迭代器提供的修改方法而不是集合自身的修改方法,此外 for-each 循环遍历的实质是迭代器,使用迭代器的 remove() 方法前必须先调用迭代器的 next() 方法且不允许调用一次 next() 方法后调用多次 remove() 方法。