使用 Iterator 遍歷集合時調用集合自身的 remove()(而非 iterator.remove()),ArrayList 和 TreeSet 表現出截然不同的拋出 ConcurrentModificationException 的時機。
// ArrayList 場景
List list = new ArrayList<>(Arrays.asList("aaa", "bbb", "ccc"));
Iterator it = list.iterator();
while (it.hasNext()) {
if (it.next().equals("bbb")) { // 遍歷到倒數第二個元素
list.remove("ccc"); // 刪除最後一個元素
}
} // 不拋出異常!
// TreeSet 場景
Set set = new TreeSet<>(Arrays.asList("ddd", "eee", "fff"));
Iterator it1 = set.iterator();
while (it1.hasNext()) {
if (it1.next().equals("fff")) { // 遍歷到最後一個元素
set.remove("eee"); // 刪除中間元素
}
} // 拋出 ConcurrentModificationException!
expectedModCount 快照,cursor 指向下一個待返回元素索引,lastRet 記錄上一次返回的索引。next():檢查 modCount,返回 elementData[cursor++],更新 lastRet。remove()(Iterator 自己的):調用 ArrayList.remove(lastRet),cursor--,lastRet = -1,同步更新 expectedModCount = modCount。remove(Object):修改 modCount,數組元素前移。關鍵點: 當遍歷到倒數第二個元素(cursor = size-1)時,集合刪除最後一個元素(index = size-1),導致數組長度減 1,但 cursor 仍指向原 size-1 位置,此時 cursor == size(新 size),hasNext() 返回 false,循環正常結束,下一次 next() 不會被調用,故不檢查 modCount,不拋異常。
NavigableMap,內部使用 TreeMap 的 PrivateEntryIterator。next 指向下一個節點,lastReturned 記錄上一個節點。next():先檢查 modCount,然後 lastReturned = next,next = successor(next)。remove(Object):修改 modCount,紅黑樹結構調整,可能導致 successor 關係變化。關鍵點: 遍歷到最後一個元素時,next() 已將 lastReturned 指向該元素,next 置為 null。隨後集合刪除其他元素導致 modCount 變化,下一次循環進入 hasNext() 不檢查,但再次調用 next() 時(若有)會檢查 modCount 拋異常。實測中因 hasNext() 為 false 循環結束,但若繼續調用 next() 則拋異常。
remove()、`add()`、`clear()` 等結構修改方法。iterator.remove(),它會同步更新迭代器內部的 expectedModCount。remove(),否則在迭代器創建後以任何方式修改集合結構,迭代器行為都是**未定義**的。// 正確刪除符合條件的元素
Iterator it = list.iterator();
while (it.hasNext()) {
String s = it.next();
if (shouldRemove(s)) {
it.remove(); // 唯一安全方式
}
}
// Java 8+ 函數式風格(內部同樣使用迭代器)
list.removeIf(this::shouldRemove);