為什麼在foreach循環中JAVA集合不能添加或刪除元素
1. 編碼強制規約
在《阿裡巴巴Java開發手冊》中,針對集合操作,有一項規定,如下:
【強制】不要在 foreach 循環裡進行元素的 remove/add 操作。remove 元素請使用 Iterator方式,如果並發操作,需要對 Iterator 對象加鎖。
public class SimpleTest { public static void main(String[] args) { List<String> list = Lists.newArrayList(); list.add("1"); list.add("2"); list.add("3"); list.add("4"); //正例 Iterator<String> iterator = list.iterator(); while (iterator.hasNext()) { String item = iterator.next(); if ("1".equalsIgnoreCase(item)) { iterator.remove(); } } //反例 for (String item : list) { if ("2".equals(item)) { list.remove(item); } } } }
2. 原因分析
在循環或迭代時,會首先創建一個迭代實例,這個迭代實例的expectedModCount 賦值為集合的modCount.
每當迭代器使⽤ hashNext() / next() 遍歷下⼀個元素之前,都會檢測 modCount 變量與expectedModCount 值是否相等,相等的話就返回遍歷;否則就拋出異常【ConcurrentModificationException】,終⽌遍歷
如果在循環中添加或刪除元素,是直接調用集合的add,remove方法【導致瞭modCount增加或減少】,但這些方法不會修改迭代實例中的expectedModCount,導致在迭代實例中expectedModCount 與 modCount的值不相等,拋出ConcurrentModificationException異常
但迭代器中的remove,add方法,會在調用集合的remove,add方法後,將expectedModCount 重新賦值為modCount,所以在迭代器中增加、刪除元素是可以正常運行的。
可以參考ArrayList中的內部私有類Itr、ListItr的源碼
public Iterator<E> iterator() { return new Itr(); } /** * An optimized version of AbstractList.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 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(); } } public E remove(int index) { rangeCheck(index); modCount++; E oldValue = elementData(index); int numMoved = size - index - 1; if (numMoved > 0) System.arraycopy(elementData, index+1, elementData, index, numMoved); elementData[--size] = null; // clear to let GC do its work return oldValue; }
3. 相關知識介紹
3.1. 什麼是快速失敗(fail-fast)?
快速失敗(fail-fast) 是 Java 集合的⼀種錯誤檢測機制。在使⽤迭代器對集合進⾏遍歷的時候,在多線程下操作⾮安全失敗(fail-safe)的集合類可能就會觸發 fail-fast 機制,導致拋出ConcurrentModificationException 異常。
另外,在單線程下,如果在遍歷過程中對集合對象的內容進⾏瞭修改的話也會觸發 fail-fast 機制。
舉個例⼦:多線程下,如果線程 1 正在對集合進⾏遍歷,此時線程 2 對集合進⾏修改(增加、刪除、修改),或者線程 1 在遍歷過程中對集合進⾏修改,都會導致線程 1 拋出ConcurrentModificationException 異常。
3.2. 什麼是安全失敗(fail-safe)呢?
采⽤安全失敗機制的集合容器,在遍歷時不是直接在集合內容上訪問的,⽽是先復制原有集合內容,在拷⻉的集合上進⾏遍歷。所以,在遍歷過程中對原集合所作的修改並不能被迭代器檢測到,故不會拋ConcurrentModificationException 異常。
到此這篇關於為什麼在foreach循環中JAVA集合不能添加或刪除元素的文章就介紹到這瞭,更多相關JAVA集合添加或刪除元素內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!
推薦閱讀:
- Java集合中的fail-fast(快速失敗)機制詳解
- Java List的remove()方法踩坑
- Java面試必備之ArrayList陷阱解析
- java數據結構ArrayList詳解
- java中LinkedList使用迭代器優化移除批量元素原理