一、使用 for 循環
List<String> list = new ArrayList<>(); list.add("1"); list.add("2"); list.add("3"); for (int i = 0; i < list.size(); i++) { System.out.println(list.size()); if ("1".equals(list.get(i))){ list.add("4"); list.remove("1"); } }
二、使用 foreach 遍歷
List<String> list = new ArrayList<>(); list.add("1"); list.add("2"); list.add("3"); for (String s : list){ if ("1".equals(s)){ list.add("4"); list.remove("1"); } }
三、使用 Iterator 迭代器
List<String> list = new ArrayList<>(); list.add("1"); list.add("2"); list.add("3"); Iterator<String> iterator = list.iterator(); while (iterator.hasNext()) { if ("1".equals(iterator.next())) { iterator.remove(); list.add("4"); list.remove("1"); } }
在第一種情況下編譯和運行都是可以的,第二種和第三種則會拋出 java.util.ConcurrentModificationException 的異常,這是為什么呢?
下面一段是來自百度知道的解釋:
邏輯上講,迭代時可以添加元素,但是一旦開放這個功能,很有可能造成很多意想不到的情況。
比如你在迭代一個 ArrayList,迭代器的工作方式是依次返回給你第0個元素,第1個元素,等等,假設當你迭代到第5個元素的時候,你突然在ArrayList的頭部插入了一個元素,使得你所有的元素都往后移動,於是你當前訪問的第5個元素就會被重復訪問。
java 認為在迭代過程中,容器應當保持不變。因此,java 容器中通常保留了一個域稱為 modCount,每次你對容器修改,這個值就會加1。當你調用 iterator 方法時,返回的迭代器會記住當前的 modCount,隨后迭代過程中會檢查這個值,一旦發現這個值發生變化,就說明你對容器做了修改,就會拋異常。
我們先看第三種情況,即使用 Iterator 迭代器對集合進行遍歷,我們以 AbstractList 為例。
首先來看一下AbstractList是如何創建Iterator的,AbstractList有一個內部類:
private class Itr implements Iterator<E> { ... }
而創建Iterator需要調用iterator()方法:
public Iterator<E> iterator() { return new Itr(); }
所以在調用集合的iterator方法之后實際上返回了一個內部類的實例。 我們看一下Itr這個類的next()方法是如何實現的:
public E next() { checkForComodification(); try { int i = cursor; E next = get(i); lastRet = i; cursor = i + 1; return next; } catch (IndexOutOfBoundsException e) { checkForComodification(); throw new NoSuchElementException(); } }
checkForComodification()方法的實現如下:
final void checkForComodification() { if (modCount != expectedModCount) throw new ConcurrentModificationException(); }
modCount表示集合的元素被修改的次數,每次增加或刪除一個元素的時候,modCount都會加一,而expectedModCount用於記錄在集合遍歷之前的modCount,檢查這兩者是否相等就是為了檢查集合在迭代遍歷的過程中有沒有被修改,如果被修改了,就會在運行時拋出ConcurrentModificationException這個RuntimeException,以提醒開發者集合已經被修改。 這就說明了為什么集合在使用Iterator進行遍歷的時候不能使用集合本身的add或者remove方法來增減元素。但是使用Iterator的remove方法是可以的,至於原因可以看一下這個方法的實現:
public void remove() { if (lastRet < 0) throw new IllegalStateException(); checkForComodification(); try { AbstractList.this.remove(lastRet); if (lastRet < cursor) cursor--; lastRet = -1; expectedModCount = modCount; } catch (IndexOutOfBoundsException e) { throw new ConcurrentModificationException(); } }
可以看到,在集合的元素被remove之后,expectedModCount被重新賦值,是的modCount總是等於expectedModCount,所以不會拋出ConcurrentModificationException異常。
而上面提到的第二種使用foreach來對集合進行遍歷本質上和第三種情況是一樣的,因為根據Oracle提供的文檔 ,foreach內部的實現機制其實就是使用的Iterator。
第一種使用for循環進行遍歷時內部使用的就是集合本身的遍歷方法,此時進行remove()可能會導致漏元素。
參看鏈接:
https://www.cnblogs.com/wunsiang/p/12765144.html