Java是不支持容器類在使用迭代器迭代過程中,使用如 list.remove(obj)方法刪除元素。否則會拋出ava.util.ConcurrentModificationException異常。應該使用iterator.remove()方法刪除當前迭代到的元素。
這是因為Java集合中有一種叫fail-fast的機制,即如果多個線程對同一個集合的內容進行操作時,則會產生fail-fast事件,即拋出異常。
比如下面代碼
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
public class Hello {
public static void main(String[] args) {
List<String> all=new ArrayList<String>();
all.add("Hello");
all.add("_");
all.add("World!!");
Iterator<String> iterator=all.iterator();
while(iterator.hasNext())
{
String str=iterator.next();
if("Hello".equals(str))
{
all.remove(str);
}
else
{
System.out.println( str+" ");
}
}
System.out.println("\n刪除\"_\"之后的集合當中的數據為:"+all);
}
}
刪除元素會拋出下面異常
這里在迭代過程中改變了原來集合的存儲內容,因此出發了fail-fast機制。
fail-fast機制是這樣設置的。以上面代碼拋出的異常為例。查看ArrayList.類的源碼,可以看到拋出異常的函數為
這里的modCount文檔是這樣解釋的
The number of times this list has been structurally modified. Structural modifications are those that change the size of the list, or otherwise perturb it in such a fashion that iterations in progress may yield incorrect results.
大概意思就是list的結構被修改的次數。結構的修改包括改變list的大小,或者其他可能使迭代結果產生錯誤的干擾行為。
expectedModCount注釋為:
The modCount value that the iterator believes that the backing List should have. If this expectation is violated, the iterator has detected concurrent modification.
因此若是在迭代過程中改變了List的結構,即使用了remove,或者等add方法則會導致expectedModCount的值與modCount的值不同,就會拋出異常。
但是有一個奇特的現象,如果我們使用List.remove(obj)方法刪除倒數第二個元素則不會超出異常。如
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
public class Hello {
public static void main(String[] args) {
List<String> all=new ArrayList<String>();
all.add("Hello");
all.add("_");
all.add("World!!");
Iterator<String> iterator=all.iterator();
while(iterator.hasNext())
{
String str=iterator.next();
if("_".equals(str))
{
all.remove(str);
}
else
{
System.out.println( str+" ");
}
}
System.out.println("\n刪除\"_\"之后的集合當中的數據為:"+all);
}
}
注意這里刪除的'_'為集合中的倒數第二個元素。同時注意代碼有,若沒有刪除此元素,則輸出此元素,即else里面的內容。結果為
不是說,不能用List.remove(obj)的方式,只能用iterator.remove()的方式刪除,這里為什么沒有拋異常。原因是這樣。
看ArrayList的hasNext()方法與next()方法的源碼
size為當前集合的大小,cursor為下個元素的索引,初始化為0
其中checkForComodification的源碼為,兩個變量的的解釋上面也已經說了。
我們的迭代器部分代碼按下面步驟執行:
all里面存儲了“Hello”,"_","world!"三個元素
- iterator.hasNext(),
- cursor=0,size=3。cursor!=size,返回true,進入while內
- String str=iterator.next(),
- checkForComodification(),此時modCount=0,expectedModCount=0,因此沒有拋出異常,
- cursor=i+1,即cursor=1
- return elementData[lastRet=i],str='Hello'
- '_'.equals(str)為False,輸出Hello
- iterator.hasNext(),
- cursor=1,size=3。cursor!=size,返回true,進入while內
- String str=iterator.next(),
- checkForComodification(),此時modCount=0,expectedModCount=0,因此沒有拋出異常,
- cursor=i+1,即cursor=2
- return elementData[lastRet=i],str='_'
- '_'.equals(str)為Ture,
- all.remove(str)
- modCount=1,因為改變了List的結構,因此modCount+1。
- iterator.hasNext(),
- cursor=2,size=2。cursor!=size,返回False,退出
可以看到,在這里就已經推出while循環了。不會在執行checkForComodification()方法,判斷List結構有沒有改變。
結論:因此只要對倒數第二個元素執行remove()方法,雖然會改變集合結構,但是因為也同時改變了集合size,使size與cursor的大小一樣,就會直接循環,不會報異常。