在Java中有時候咱們會須要對List裏面的符合某種業務的數據進行刪除,可是若是不瞭解裏面的機制就容易掉入「陷阱」致使遺漏或者程序異常。本文以代碼例子的方式進行說明該問題。java
一、採用索引下標遍歷的方式併發
咱們看這段示例代碼:spa
1 public class ListRemoveTest { 2 3 public static void main(String[] args) { 4 List<Integer> list = new ArrayList<Integer>(); 5 list.add(1); 6 list.add(2); 7 list.add(2); 8 list.add(3); 9 list.add(4); 10 11 for (int i = 0; i < list.size(); i++) { 12 if (2 == list.get(i)) { 13 list.remove(i); 14 } 15 System.out.println(list.get(i)); 16 } 17 System.out.println("最後結果=" + list.toString()); 18 } 19 }
該代碼運行結果以下:線程
1
2
3
4
最後結果=[1, 2, 3, 4]code
咱們是想刪除等於2的元素,但結果顯示只刪除了一個2,另外一個2被遺漏了,緣由是:刪除了第一個2後,集合裏的元素個數減1,後面的元素往前移了1位,致使了第二個2被遺漏了。對象
二、採用For循環遍歷的方式blog
1 public class ListRemoveTest { 2 3 public static void main(String[] args) { 4 List<Integer> list = new ArrayList<Integer>(); 5 list.add(1); 6 list.add(2); 7 list.add(2); 8 list.add(3); 9 list.add(4); 10 11 for (Integer value : list) { 12 if (2 == value) { 13 list.remove(value); 14 } 15 System.out.println(value); 16 } 17 System.out.println("最後結果=" + list.toString()); 18 } 19 }
程序運行結果:索引
1
2
Exception in thread 「main」 java.util.ConcurrentModificationException
at java.util.AbstractList$Itr.checkForComodification(AbstractList.java:372)
at java.util.AbstractList$Itr.next(AbstractList.java:343)
at ListRemoveTest.main(ListRemoveTest.java:32)rem
從運行結果看到程序拋ConcurrentModificationException。get
JDK的API中對該異常描述道:
public class ConcurrentModificationException extends RuntimeException當方法檢測到對象的併發修改,但不容許這種修改時,拋出此異常。
例如,某個線程在 Collection 上進行迭代時,一般不容許另外一個線性修改該 Collection。一般在這些狀況下,迭代的結果是不肯定的。若是檢測到這種行爲,一些迭代器實現(包括 JRE 提供的全部通用 collection 實現)可能選擇拋出此異常。執行該操做的迭代器稱爲快速失敗 迭代器,由於迭代器很快就徹底失敗,而不會冒着在未來某個時間任意發生不肯定行爲的風險。
注意,此異常不會始終指出對象已經由不一樣 線程併發修改。若是單線程發出違反對象協定的方法調用序列,則該對象可能拋出此異常。例如,若是線程使用快速失敗迭代器在 collection 上迭代時直接修改該 collection,則迭代器將拋出此異常。
注意,迭代器的快速失敗行爲沒法獲得保證,由於通常來講,不可能對是否出現不一樣步併發修改作出任何硬性保證。快速失敗操做會盡最大努力拋出 ConcurrentModificationException。所以,爲提升此類操做的正確性而編寫一個依賴於此異常的程序是錯誤的作法,正確作法是:ConcurrentModificationException 應該僅用於檢測 bug。
Java中的For each實際上使用的是iterator進行處理的。而iterator是不容許集合在iterator使用期間刪除的。因此致使了iterator拋出了ConcurrentModificationException 。
三、在遍歷List過程當中刪除元素的正確作法
1 public class ListRemoveTest { 2 3 public static void main(String[] args) { 4 List<Integer> list = new ArrayList<Integer>(); 5 list.add(1); 6 list.add(2); 7 list.add(2); 8 list.add(3); 9 list.add(4); 10 11 Iterator<Integer> it = list.iterator(); 12 while (it.hasNext()) { 13 Integer value = it.next(); 14 if (2 == value) { 15 it.remove(); 16 } 17 System.out.println(value); 18 } 19 System.out.println("最後結果=" + list.toString()); 20 } 21 }
輸出結果:
1
2
2
3
4
最後結果=[1, 3, 4]
咱們看到兩個2所有被刪除了,最後結果剩下1,3,4徹底正確。
但對於iterator的remove()方法,也有須要咱們注意的地方:
一、每調用一次iterator.next()方法,只能調用一次remove()方法。
二、調用remove()方法前,必須調用過一次next()方法。
如下是JDK-API中對於remove()方法的描述:
void remove()
從迭代器指向的集合中移除迭代器返回的最後一個元素(可選操做)。每次調用 next 只能調用一次此方法。若是進行迭代時用調用此方法以外的其餘方式修改了該迭代器所指向的集合,則迭代器的行爲是不明確的。
拋出:
UnsupportedOperationException
- 若是迭代器不支持 remove 操做。IllegalStateException
- 若是還沒有調用 next 方法,或者在上一次調用 next 方法以後已經調用了remove 方法。