java - 遍历list的时候为什么不能修改呢?
巴扎黑
巴扎黑 2017-04-17 14:57:37
0
4
931

求一个明确的解答,通俗易懂的哈

巴扎黑
巴扎黑

全部回覆(4)
大家讲道理

具體語言實作不同,這裡是一些語言無關的點

  • 如果你在遍歷時往數組增加數據,會導致遍歷不完整(因為增加了新成員長度變了),或者死循環(因為總是有新的進去)

  • 如果在遍歷時刪除數據,則會導致數組存取越界(因為長度縮短了,指針指向了一個已經標示為空的區域)

  • 如 @Terry_139061 的答案,如果你只是在遍歷時修改這個節點本身的數據,一般來說是安全的(當然需要看具體場景)

Ty80

你的問題描述得不太準確,標籤是javalisp,只寫過一點 racket 程式碼就不談論 lisp 了,以下預設修改為 list 的 add/remove 操作

  • 首先, java 裡面有很多種 list :

      java.util.ArrayList;
      java.util.LinkedList;
      java.util.Stack;
      java.util.Vector;
      java.util.concurrent.CopyOnWriteArrayList;

    其中 CopyOnWriteArrayList在遍歷的時候修改是不會出錯的,實作方法是讀寫分離,參考維基 Copy-on-write

  • 其次, java 裡面有好幾種遍歷方式:

    for
    iterator
    foreach
    • for,在 for 迴圈中修改並沒有問題,除非你把要存取的物件刪除,陣列越界,或一直add產生無窮序列

    • iterator,你用 iterator.remove() 是不會有問題的,因為 iterator.remove() 會設定

      this.expectedModCount = ArrayList.this.modCount;//(1)

      這樣之後遍歷執行 iterator.next() 就不會拋異常

      public E next() {
      this.checkForComodification();
      ...
      }
      
      final void checkForComodification() {
      if(ArrayList.this.modCount != this.expectedModCount) {
          throw new ConcurrentModificationException();
      }
      }
    • foreach,本質上是隱式的iterator (可以用javap -c 比較字節碼),由於沒有重新設定expectedModCount ,當你使用list.remove() 後遍歷執行iterator.next( ) 時就會報ConcurrentModificationException

  • 最後,這裡面有一個特別的地方,就如果刪除的是list 裡倒數第二個值,這樣觸發hasNext() 的時候結果正好為false 退出循環不繼續執行next() 也就不會報錯

        public boolean hasNext() {
            return this.cursor != ArrayList.this.size;
        }

PS:使用的JDK是java-8-openjdk-amd64

Peter_Zhu

可以修改呀,下面的程式會印3個aaaaa

List<String> list = new ArrayList<String>();
list.add("hello");
list.add("world");
list.add("!");
for (int i = 0; i < list.size(); i++) {
    list.set(i, "aaaaa");
}
for (int i = 0; i < list.size(); i++) {
    System.out.println(list.get(i));
}

你是用什麼方法遍歷的,請貼出程式碼來,好幫你分析

小葫芦

從java的角度回答這個問題, Iterator模式是用來遍歷集合類別的標準存取方法。它可以把存取邏輯從不同類型的集合類別中抽象化出來,從而避免向客戶端揭露集合的內部結構。
例如,如果沒有使用Iterator,遍歷一個陣列的方法是使用索引:

  for(int i=0; i<array.size(); i++) { ... get(i) ... } 
  • 客戶端都必須事先知道集合的內部結構,存取程式碼和集合本身是緊密耦合,無法將存取邏輯從集合類別和客戶端程式碼中分離出來,每一種集合對應一種遍歷方法,客戶端程式碼無法重複使用。

  • 更恐怖的是,如果以後需要把ArrayList更換為LinkedList,則原來的客戶端程式碼必須全部重寫。要解決以上問題,Iterator模式總是用同一種邏輯來遍歷集合:

             for(Iterator it = c.iterater(); it.hasNext(); ) { ... }

     - 奧秘在於客戶端本身不維護遍歷集合的"指針",所有的內部狀態(如當前元素位置,是否有下一個元素)都由Iterator來維護,而這個Iterator由集合類通過工廠方法生成,因此,它知道如何遍歷整個集合。客戶端從不直接和集合類別打交道,它總是控制Iterator,向它發送"向前","向後","取當前元素"的命令,就可以間接遍歷整個集合。

  • 當使用Iterator對集合元素進行迭代的時候,collection並不是把集合元素本身傳給了迭代變量,而是把集合元素的值傳給了迭代變量。所以修改迭代變數的值對集合元素本身的值沒有任何改變。迭代器Iterator不保存對象,它依附於Collection對象,僅用於遍歷集合。迭代器Iterator採用的是快速-失敗(fail-fast)機制,一旦在迭代的過程中偵測到該集合已經被修改,程式立即引發java.util.ConcurrentModificationException,而不是顯示修改後的結果。這樣可以避免共享資源而引發的潛在問題。

public class TestCollections {  
  
    public static void main(String[] args) {  
        List<String> list = new ArrayList<String>();  
        for (int i = 0; i < 10; i++) {  
            list.add("hello_" + i);  
        }  
        Iterator<String> iterator = list.iterator();  
        while (iterator.hasNext()) {  
            String str = iterator.next();  
            System.out.println(str);  
            if (str.equals("hello_5")) {  
//                iterator.remove();  
                list.remove(str);  
            }  
            str = "hehe";  
        }  
        System.out.println(list);  
    }  
}
  • 執行{ str = "hello" } 語句,對外部的元素沒有任何影響,執行iterator.remove()會刪除當前的迭代對象.執行list.remove(str) ,會報java.util.ConcurrentModificationException ,因為他偵測到集合的元素被修改了.

熱門教學
更多>
最新下載
更多>
網站特效
網站源碼
網站素材
前端模板