搜尋

首頁  >  問答  >  主體

java - 遍历list的时候为什么不能修改呢?

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

巴扎黑巴扎黑2770 天前933

全部回覆(4)我來回復

  • 大家讲道理

    大家讲道理2017-04-17 14:59:37

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

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

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

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

    回覆
    0
  • PHP中文网

    PHP中文网2017-04-17 14:59:37

    你的問題描述得不太準確,標籤是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

    回覆
    0
  • 天蓬老师

    天蓬老师2017-04-17 14:59:37

    可以修改呀,下面的程式會印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));
    }

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

    回覆
    0
  • 高洛峰

    高洛峰2017-04-17 14:59:37

    從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 ,因為他偵測到集合的元素被修改了.

    回覆
    0
  • 取消回覆