Java中ArrayList的刪除元素總結


Java中循環遍歷元素,一般有for循環遍歷,foreach循環遍歷,iterator遍歷。

  • 先定義一個List對象
List<String> list = new ArrayList<>();
list.add("1");
list.add("2");
list.add("3");

一、普通for循環遍歷

for (int i = 0; i < list.size(); i++) {
    System.out.println(i + ":" + list.get(i));
    String s = list.get(i);
    if ("1".equals(s)) {
        list.remove(s);
//        i--;
    }
}
System.out.println(list);

輸出結果為

0:1
1:3
[2, 3]

這種刪除方法明顯有問題,遺漏了被刪除元素后的一個元素。
這種情況下,如果被刪除元素切好是List中最后一個元素,則輸出結果恰好正常。
解決方法:
遺漏元素是因為刪除元素后,List的size已經減1,但i不變,則i位置元素等於被跳過,不在循環中處理。
若if代碼塊中調用remove函數后,加上i--,則能避免這種錯誤。

二、Iterator遍歷

Iterator<String> iterator = list.iterator(); 
while (iterator.hasNext()){
    String str = iterator.next();
    System.out.println(str);
    if("2".equals(str)) {
        iterator.remove(); 
    }
}
System.out.println(list);

輸出結果為

1
2
3
[1, 3]

結論:
最安全的遍歷中刪除元素方法。
借用了Iterator,刪除元素用的也是Iterator的remove方法,而不是List中的。

三、foreach循環遍歷

for (String s : list) {
    System.out.println(s);
    if ("2".equals(s)) {
        list.remove(s);
    }
}

現象:
刪除元素2:正常輸出

1  
2  
[1, 3]

刪除元素1或3:報錯

java.util.ConcurrentModificationException  
at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:909)  
at java.util.ArrayList$Itr.next(ArrayList.java:859)

foreach的本質
通過反編譯,foreach的代碼實現如下:

Iterator itr3 = list.iterator();
while(itr3.hasNext()) {
    String s = (String)itr3.next();
    System.out.println(s);
    if ("2".equals(s)) {
        list.remove(s);
    }
}

對比后發現,foreach實質上也是使用Iterator進行遍歷。
不同的地方在於,一個使用Iterator的刪除方法,一個使用List的刪除方法。
問題出在 list.remove(s); 代碼中。
我們查看一下ArrayList的報錯相關代碼。代碼如下:

public boolean hasNext() {
    return cursor != size;
}
public E next() {
    checkForComodification();//859行
    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();//909行
}
public boolean remove(Object o) {
    ···
    fastRemove(index);
    ···
}
private void fastRemove(int index) {
    modCount++;
    ···
}

其中size和modCount為ArrayList屬性,cursor和expectedModCount為ArrayList.Itr屬性。

size: List長度
modCount: List在結構上被修改的次數
cursor: Itr中下一個被返回的元素的下標
expectedModCount: 屬於ArrayList.Itr,與modCount類似,初始化值等於modCount值。

輸出分析

1. 報錯是因為remove方法改變了modCount,導致next方法時checkForComodification檢查不通過,拋出異常。
2. 移除2時正常:因為2剛好是倒數第二個元素,移除后size-1,在hasNext方法中已結束循環,不在調用next方法。雖然不報錯,但會使最后一個元素被跳過,沒有進入循環。
3. 移除1或3失敗略有不同:remove(3)后,size減1,cursor已經比size大1,但由於hasNext方法是 cursor!=size,還是會進入循環,在next方法中才會報錯。如果hasNext方法是 cursor>size ,移除3的情形會類似於移除2(不報錯,直接退出進入循環)。

四、結論及其他

  1. 集合中遍歷移除元素保險起見都是使用Iterator,這沒什么好爭議的。寫這么多,只是為了看代碼,探究其底層原因。
  2. Java8中的刪除方法removeIf,如下,其實也是使用Iterator。
    list.removeIf(e->e.equals("2"));
    
  3. Java8中使用如下方式刪除,本質上是new了一個List,結果已經不是原List了。類似的,上述的遍歷中,new一個新的List,將需要的元素add進入也是可行的。
    list = list.stream().filter(l->!l.equals("2")).collect(Collectors.toList());
    


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM