Java Iterator 和 ListIterator 迭代器 以及 併發修改異常 ConcurrentModificationException

Java Iterator ListIterator 迭代器 以及 併發修改異常 ConcurrentModificationException

  • 迭代器內部定義了一個 cursor 變量,用來指示當前遍歷到了什麼位置。每次調用 next()previous() 對該值做出相應的 遞增或遞減 操作,以此實現順序迭代。
    這裏寫圖片描述

  • 當調用 hasNext() 方法時,判斷 cursor 變量是否等於集合大小,如果不等於,說明後面還有元素可以正序迭代。
    這裏寫圖片描述

  • 當調用 hasPrevious() 方法時,判斷 cursor 變量是否小於 0 ,如果不等於,說明前面還有元素可以倒序迭代。
    這裏寫圖片描述

Iterator 迭代器

  • 方法示例

    ArrayList<Character> list = new ArrayList<>();
    // 添加元素
    for (char c = 'A'; c <= 'G'; c++) {
        list.add(c);
    }
    
    Iterator<Character> it = list.iterator();
    
    // hasNext() 判斷是否還有元素可以迭代
    while (it.hasNext()) {
        char c = it.next(); // next() 返回下一個元素
    
        if (c == 'C') {
            it.remove(); // remove() 移除元素
        } else {
            System.out.print(c + ", ");
        }
    }
  • 運行結果
    運行結果

  • Iterator 的方法

    方法 功能
    hasNext() 判斷集合列表中是否還有可以迭代的元素
    next() 返回下一個元素
    remove() 移除最近返回的一個元素

ListIterator 迭代器

  • 方法示例

    ArrayList<Integer> list = new ArrayList<>();
    // 添加元素
    for (int i = 1; i <= 3; i++) {
        list.add(i);
    }
    
    ListIterator<Integer> li = list.listIterator();
    
    boolean flag = true;
    
    // 如果是正序迭代 或者 有前一個可以迭代的元素
    while (flag || li.hasPrevious()) {
        int index = 0;
        int ele = 0;
    
        if (flag) {
            index = li.nextIndex(); // nextIndex() 返回下一個元素的索引
            ele = li.next(); // next() 返回下一個元素
        } else {
            index = li.previousIndex(); // previousIndex 返回上一個元素的索引
            ele = li.previous(); // previous() 返回上一個元素
        }
    
        if (ele == 1) {
            // 如果迭代到的元素是 1 ,則將該元素替換成 0
            li.set(0); // set() 用指定元素替換最後返回的元素
        } else if (ele == 3) {
            li.remove(); // remove() 移除最後返回的元素
        }
    
        System.out.println("(" + index + ") = " + ele);
    
        // 判斷是否還有下一個可以迭代的元素
        if (!li.hasNext()) {
            flag = false;
            li.add(10); // add() 添加一個元素
        }
    
    }
  • 運行結果
    運行結果
  • ListIterator 的方法

    方法 功能
    hasNext() 判斷是否還有下一個元素可以迭代
    next() 返回下一個元素
    hasPrevious() 判斷是否還有上一個元素可以迭代
    previous() 返回上一個元素
    add() 返回上一個元素
    set(E e) 用指定的元素替換最近返回的元素
    remove() 移除最近返回的元素

併發修改異常

  • 簡單來講,併發修改異常就是在對集合元素進行迭代的時候,集合本身發生了改變,所以拋出此異常。先來看一個示例。

  • 示例

    ArrayList<Character> list = new ArrayList<>();
    // 添加元素
    for (char c = 'A'; c <= 'G'; c++) {
        list.add(c);
    }
    
    // 獲得集合的迭代器
    Iterator<Character> it = list.iterator();
    
    while (it.hasNext()) {
        char c = it.next();
    
        if (c == 'D') {
            list.remove(new Character(c));
        }
    
        System.out.println(c);
    }
  • 運行結果
    這裏寫圖片描述

  • 原因

    • 上面已經提到,併發修改異常 是在迭代過程中,被迭代的集合發生了變化才引發的。那麼看下面代碼中用 藍色 標識的代碼,這裏顯然對集合中的元素進行了 移除操作

      這裏寫圖片描述

    • 需要注意的是,該異常並不是移除操作引發的 ,而是上面用 紅色 標識的代碼,也就是下一次循環去獲取集合元素的時候引發的。具體是怎麼回事這要講一下源碼,看下圖。

      這裏寫圖片描述

    • 源代碼 next() 方法中,調用了一個 checkForComodification() 方法,這是什麼呢? 繼續跟下去看一下。

      這裏寫圖片描述

    • 看上圖,代碼顯示,如果 modCount 不等於 expectedModCount 則拋出 併發修改異常 。那麼這兩個變量都是什麼意思呢?我們繼續分析源碼。

      這裏寫圖片描述
      集合在創建時: modCount 的值默認是 0 那麼這個值在什麼情況下會發生變化呢?

    • 首先,我們看添加元素 add() 方法。
      這裏寫圖片描述
      這裏調用了一個 ensureCapacityInternal() 方法,跟下去看一下。

      這裏寫圖片描述

    • 看上面的圖片,在 調用 ensureCapacityInternal() 方法後,ensureCapacityInternal() 方法裏又調用了一個 ensureExplicitCapacity() 方法,而 modCount 值的改變就是發生在這個方法裏,當然,在 add() 方法調用 ensureCapacityInternal() 之前,元素的添加操作就以及完成了, 實際上包括 remove()set() 操作最後都會遞增 modCound。也就是說,只要對集合進行了操作,這個 modCount 值就會遞增一次。

    • 現在,我們搞清楚了 modCount 是怎麼回事,那 expectedModCount 又是怎麼回事呢?看下圖。

      這裏寫圖片描述

      這裏寫圖片描述

    • 當我們調用集合的 iterator() 方法獲得迭代器時,底層實際上是給我們 new 了一個 Itr 內部類對象,這個內部類實現了 Iterator 接口。

    • 看上圖箭頭標識 1 的位置,在創建迭代器 Itr 對象的時候,先用 expectedModCount 把集合的 modCount 值記錄下來。

    • 講到這裏,再返回上面看一下前面說到的 調用迭代器的 next() 爲什麼引發 併發修改異常 就明白怎麼回事了。

總結

  • 實現了 Iterator 迭代器接口的集合都可以使用迭代器對其元素進行遍歷。

  • ListIterator 可以再迭代時對集合進行 add、set、remove 操作,而 Iterator 迭代器只能在迭代時對集合進行 remove 操作。

  • 不允許在迭代時對集合進行操作,當然,使用迭代器本身提供的方法除外,原因這裏不再圖文分析,簡單說一下吧,在使用迭代器本身提供的方法對集合操作後,expectedModCount 的值也會相應遞增,所以下次檢查是就不會有問題。感興趣的朋友可以自己去看一下源代碼。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章