1. 程式人生 > >Java 遍歷集合刪除元素?

Java 遍歷集合刪除元素?

  • 問題及分析
  • 正確刪除集合元素

問題及分析

注:在瀏覽阿里巴巴Java開發手冊時,自己測試Java遍歷集合並刪除元素時發現有些巧合以及總結

先寫開發手冊裡一個例子,大家猜一下以下程式碼的輸出

List<String> list = new ArrayList<>();
list.add("1");
list.add("2");
list.add("3");

System.out.println("list original size is " + list.size());

for (String item : list
) { if ("1".equals(item)) { list.remove(item); } } System.out.println("list size is " + list.size());
List<String> list = new ArrayList<>();
list.add("1");
list.add("2");
list.add("3");

System.out.println("list original size is " + list.size());

for (String item : list
) { if ("1".equals(item)) { list.remove(item); } } System.out.println("list size is " + list.size());

上面兩個例子的輸出結果是什麼?意外嗎?
1,2可以成功;1, 2, 3刪除丟擲異常

  • 第一個:
    list original size is 2
    list size is 1
  • 第二個:
    list original size is 3
    java.util.ConcurrentModificationException

我們可以看一下此處程式碼的反編譯程式碼:

List<String> list = new ArrayList();
list.add("1");
list.add("2");
// list.add("3");
System.out.println("list original size is " + list.size());
Iterator var2 = list.iterator();

while(var2.hasNext()) {
    String item = (String)var2.next();
        if ("1".equals(item)) {
            list.remove(item);
        }
}

System.out.println("list size is " + list.size());

通過檢視反編譯程式碼,發現我們使用的foreach遍歷仍然是迭代器Iterator遍歷,而ArrayList中Iterator原始碼:

 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;

     Itr() {}

     public boolean hasNext() {
         return cursor != size;   // 遊標
     }

     @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];
     }

     public void remove() {
         if (lastRet < 0)
             throw new IllegalStateException();
         checkForComodification();

         try {
             ArrayList.this.remove(lastRet);
             cursor = lastRet;
             lastRet = -1;
             expectedModCount = modCount;   // 修改預期值(迭代器刪除的重要參考)
         } catch (IndexOutOfBoundsException ex) {
             throw new ConcurrentModificationException();
         }
     }
     ......
  }

modCount是集合新增,刪除等改變集合結構的次數(改變集合大小),expectedModCount是預期的變化次數;
分析一下:
[“1”, “2”] : 當”1”被遍歷刪除後,遊標cursor的值從0變為1,集合長度也變為1,這是hasNext返回false,比較表示沒有下一個元素,結束遍歷;
[“1”, “2”, “3”] : 如上,當”1”遍歷刪除後,遊標cursor從0變為1,集合長度變為2,hasNext返回true,執行remove時,checkForComodification()方法驗證是否同時修改,此方法表modCount != expectedModCount,modCount是3,expectedModCount也是3,而在刪除後modCount變為4,而hasNext()方法不返回false,next()方法呼叫時就會丟擲異常;
注:由其遊標變化規律可以看出,如果hasNext提前結束,不執行後面的next取資料,就可以刪除集合元素,故可以刪除集合中倒數第二個元素而不丟擲異常(實踐也如此)


正確刪除集合元素

  • 迭代器方式
 public void positiveForEachTest() {
      List<String> list = new ArrayList<>();
      list.add("w");
      list.add("li");
      list.add("z");

      System.out.println("list original size is " + list.size());

      Iterator<String> iterator = list.iterator();
      while (iterator.hasNext()) {
          String item = iterator.next();
          if ("li".equals(item)) {
              iterator.remove();
          }
      }

      System.out.println("after list remove elem `li`, it's size is " + list.size());
  }