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 的值也會相應遞增,所以下次檢查是就不會有問題。感興趣的朋友可以自己去看一下源代碼。