問:簡單說說 Iterator 和 ListIterator 的區(qū)別?
答:區(qū)別主要如下酥夭。
(1)ListIterator 有 add() 方法枯芬,可以向 List 中添加對象,而 Iterator 不能采郎。
(2)ListIterator 和 Iterator 都有 hasNext() 和 next() 方法千所,可以實現(xiàn)順序向后遍歷,但是 ListIterator 有 hasPrevious() 和 previous() 方法蒜埋,可以實現(xiàn)逆向(順序向前)遍歷淫痰,Iterator 就不可以。
(3)ListIterator 可以定位當(dāng)前的索引位置整份,通過 nextIndex() 和 previousIndex() 可以實現(xiàn)待错,Iterator 沒有此功能。
(4)都可實現(xiàn)刪除對象烈评,但是 ListIterator 可以實現(xiàn)對象的修改火俄,通過 set() 方法可以實現(xiàn),Iterator 僅能遍歷讲冠,不能修改瓜客。
(5)ListIterator 是 Iterator 的子接口。
注意:容器類提供的迭代器都會在迭代中間進(jìn)行結(jié)構(gòu)性變化檢測竿开,如果容器發(fā)生了結(jié)構(gòu)性變化谱仪,就會拋出 ConcurrentModificationException
,所以不能在迭代中間直接調(diào)用容器類提供的 add否彩、remove 方法疯攒,如需添加和刪除,應(yīng)調(diào)用迭代器的相關(guān)方法列荔。
問:為什么使用 for-each 時調(diào)用 List 的 remove 方法元素會拋出 ConcurrentModificationException 異常敬尺?
答:首先 Java 提供了一個 Iterable 接口返回一個迭代器,常用的 Collection<E>贴浙、List<E>砂吞、Set<E> 等都實現(xiàn)了這個接口,該接口的 iterator() 方法返回一個標(biāo)準(zhǔn)的 Iterator 實現(xiàn)悬而,實現(xiàn) Iterable 接口允許對象成為 for-each 語句的目標(biāo)來遍歷底層集合序列呜舒,因此使用 for-each 方式遍歷列表在編譯后實質(zhì)是迭代器的形式實現(xiàn)。之所以會出現(xiàn) ConcurrentModificationException 異常我們需要去看下最常見的 ArrayList 中 iterator() 方法的實現(xiàn)(別的集合 iterator 類似)笨奠,如下:
private class Itr implements Iterator<E> {
protected int limit = ArrayList.this.size; //集合列表的個數(shù)尺寸
int cursor; //下一個元素的索引位置
int lastRet = -1; //上一個元素的索引位置
int expectedModCount = modCount;
public boolean hasNext() {
return cursor < limit;
}
@SuppressWarnings("unchecked")
public E next() {
//modCount用于記錄ArrayList集合的修改次數(shù)袭蝗,初始化為0唤殴,
// 每當(dāng)集合被修改一次(結(jié)構(gòu)上面的修改,內(nèi)部update不算)到腥,
// 如add朵逝、remove等方法,modCount + 1乡范,所以如果modCount不變配名,
// 則表示集合內(nèi)容沒有被修改。
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(); //調(diào)用一次cursor加一次
cursor = i + 1;
//返回當(dāng)前一個元素
return (E) elementData[lastRet = i];
}
public void remove() {
//lastRet每次在remove成功后都需要在next()中重新賦值晋辆,
// 否則調(diào)用一次后再調(diào)用為-1異常渠脉,因此使用迭代器的remove方法
// 前必須先調(diào)用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();
}
}
......
}
通過上面的源碼發(fā)現(xiàn)迭代操作中都有判斷 modCount != expectedModCount
的操作瓶佳,在 ArrayList 中 modCount 是當(dāng)前集合的版本號芋膘,每次修改(增、刪)集合都會加 1霸饲,expectedModCount 是當(dāng)前迭代器的版本號为朋,在迭代器實例化時初始化為 modCount,所以當(dāng)調(diào)用 ArrayList.add()
或 ArrayList.remove()
時只是更新了 modCount 的狀態(tài)厚脉,而迭代器中的 expectedModCount 未修改习寸,因此才會導(dǎo)致再次調(diào)用 Iterator.next() 方法時拋出 ConcurrentModificationException
異常。而使用 Iterator.remove()
方法沒有問題是因為 Iterator 的 remove() 方法中有同步 expectedModCount 值傻工,所以當(dāng)下次再調(diào)用 next() 時檢查不會拋出異常霞溪。這其實是一種快速失敗機制,機制的規(guī)則就是當(dāng)多個線程對 Collection 進(jìn)行操作時若其中某一個線程通過 Iterator 遍歷集合時該集合的內(nèi)容被其他線程所改變精钮,則拋出 ConcurrentModificationException 異常威鹿。
因此在使用 Iterator 遍歷操作集合時應(yīng)該保證在遍歷集合的過程中不會對集合產(chǎn)生結(jié)構(gòu)上的修改剃斧,如果在遍歷過程中需要修改集合元素則一定要使用迭代器提供的修改方法而不是集合自身的修改方法轨香,此外 for-each 循環(huán)遍歷的實質(zhì)是迭代器,使用迭代器的 remove() 方法前必須先調(diào)用迭代器的 next() 方法且不允許調(diào)用一次 next() 方法后調(diào)用多次 remove() 方法幼东。