最近在看《阿里巴巴Java開發手冊》時,(五)集合處理-7【強制】不要在foreach
循環裏進行元素的remove/add
操做。這一條規則中,有一段很是有意思的代碼java
List<String> list = new ArrayList<>();
list.add("1");
list.add("2");
for (String item : list) {
if ("1".equals(item)) {
list.remove(item);
}
}複製代碼
執行這段代碼,沒有任何問題,foreach循環不會拋出任何異常,並且元素移除成功。數組
可是,當咱們把if ("1".equals(item))
改爲if ("2".equals(item))
時,程序會拋出ConcurrentModificationException。以下圖所示:測試
經過分析foreach
實現機制以及拋出的代碼行數,很容易得知其緣由。this
咱們知道,foreach
是Java
的一個語法糖,經過反編譯測試類生成的代碼,咱們知道它其實是由Iterator實現的。反編譯的結果以下:spa
List<String> list = new ArrayList();
list.add("1");
list.add("2");
Iterator var2 = list.iterator();
while (var2.hasNext()) {
String item = (String)var2.next();
if ("2".equals(item)) {
list.remove(item);
}
}複製代碼
結合程序拋出的異常棧信息,咱們能夠得知,異常是在String item = (String)var2.next();
這一行拋出的(只有這一行調用了next()
操做)。code
找到報錯的代碼行,咱們再來分析報錯的緣由。既然在String item = (String)var2.next();
報錯,說明while循環的條件是經過了。也就是說,咱們在移除了最後一個元素後,迭代器居然並未感知到,hasNext()
依然返回true。咱們來看看ArrayList.Iterator
的hasNext()
是如何實現的:cdn
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; // 修改次數,modCount來自於外部的ArrayList類;
// 而expectedModCount則由Itr記錄
...
public boolean hasNext() {
return cursor != size; // size來自於外部的ArrayList類
}
...
}複製代碼
ArrayList.iterator().hasNext()
的實現很簡單,就是判斷當前下標是否與數組大小相同。這就說明,當刪除ArrayList
最後一個元素時,cursor != size
。爲何會這樣呢?咱們再來看一下next()
的實現:blog
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; // 修改次數,modCount來自於外部的ArrayList類;
// 而expectedModCount則由Itr記錄
...
@SuppressWarnings("unchecked")
public E next() {
checkForComodification();
int i = cursor;
if (i >= size)
throw new NoSuchElementException();
Object[] elementData = ArrayList.this.elementData;
if (i >= elementData.length) {
throw new ConcurrentModificationException();
cursor = i + 1;
return (E) elementData[lastRet = i];
}
...
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}複製代碼
咱們看cursor
值 的變化,實際上,cursor
的值在每次進行元素值對比前,就已經跳到下一個位置。當咱們刪除第1個元素後,cursor
的值是1,與當前ArrayList.size(1)
是相等的,hasNext()
返回false
,while
循環結束;而當咱們刪除第2個元素時,cursor
的值是2,與當前ArrayList.size(1)
不相等,所以hasNext()
返回true
。element
咱們再看next()
爲何拋出異常。ArrayList.Itr
在next()
操做中,首先檢查modCount
是否等於expectedModCount
,若是不相等,則拋出ConcurrentModificationException
,程序中的異常也正是在此處拋出的。expectedModCount
由ArrayList.Itr
維護,而咱們程序中用ArrayList.remove()
移除元素,修改的是ArrayList.modCount
,這兩個值顯然不相等(expectedModCount == 0
而modCount == 1
)。正是因爲hasNext()
錯誤地返回了true
,致使咱們調用next()
而拋出了異常。開發
從上文的分析中咱們能夠得出結論,因爲foreach
語法糖的特性,它並非嚴格意義上的java
語法,不能像Java標準語法那樣靈活地使用。從其生成的代碼來看,foreach循環不能進行元素的修改操做,而只能進行元素的遍歷操做。