迭代器 Iterator 總結
今天一位朋友在用迭代器時很鬱悶為什麼會會報Java.util.ConcurrentModificationExceptiond異常, 於是寫下這篇部落格,想詳細的講講Java裡面 的迭代器.
Iterator簡單的來說就是遍歷, 遍歷什麼? 遍歷集合元素等.
Iterator介面共有四個方法:
public interface Iterator<E>{ boolean hasNext():如果被迭代的集合還元素沒有被遍歷,則返回true。 Object next():返回集合裡下一個元素。 void remove() :刪除集合裡上一次next方法返回的元素 void forEachRemaining(Consumer action),這是Java 8為Iterator新增的預設方法,該方法可使用Lambda表示式來遍歷集合元素。 } }
如果只是普通的迭代輸出,那麼不會出現什麼問題.但是在現實需求中這往往沒有什麼用,我們還需要做一些額外的操作,比如說
add/remove等。 但是這樣做很可能會出現一些很尷尬的事情, 下面我給出一段正確的迭代刪除程式碼:
import java.util.*; public class TestIterator { public static void main(String[] args) { // 建立集合、新增元素 Collection<String> books = new ArrayList(); books.add("測試1"); books.add("測試2"); books.add("測試3"); Iterator it = books.iterator(); while(it.hasNext()) { // it.next()方法返回的資料型別是Object型別,因此需要強制型別轉換 String book = (String)it.next(); System.out.println(book); if (book.equals("測試2")) { // 從集合中刪除上一次next方法返回的元素 it.remove(); } } System.out.println(books); } }
上面的程式碼經過測試是可以正確刪除元素的,有疑問的程式碼估計也就是這一句了
Iterator it = books.iterator();
這段程式碼的意思是 獲得List物件的迭代器,然後通過迭代器來遍歷List物件內儲存的元素.
接著我們就可以呼叫他的 hasNext方法和next方法去操作他了.
接著貼出一段錯誤的程式碼這是在我工作機上面測試的程式碼 jdk是1.8 (當然我相信以前的jdk結果應該也是這樣):
那麼為什麼二種刪除方式一個有錯一個沒錯呢(這個問題難倒了工作三年的一個朋友). 經過剛剛的測試,我們發現用迭代器本身刪除是可以的,但是在迭代器中
對list本身進行刪除就不行了, 對於這種莫名其妙的問題,我們從原始碼入手, 首先根據上面的我們先看看ArrayList他是怎麼返回Iterator例項的,最終原始碼如下:
在ArrayList類中有一個私有類Private Itr 我們注意到expectedModCount這個欄位,他的初始值是等於modCount的。 那modCount又是幹啥的呢 最直接的。 看原始碼:
我們從原始碼可以對集合的每一次 add / remove 都會觸發一下這個欄位,而上面Itr的remove 不管在刪除還是新增都會觸發一個方法(csdn上截圖太麻煩我直接寫了 ) checkForComodification();這個方法, 大家可以看看上面的截圖而這個 checkForComodification();方法中只有一個功能
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
那就是丟擲異常~~~~
所以大家應該懂了那段錯誤的程式碼為什麼會錯誤了吧。。 因為二個欄位不相等啊。 沒有同步啊。
如果你問我為什麼要這麼做,我只能這麼跟你說:
Iterator 是工作在一個獨立的執行緒中,並且擁有一個 mutex 鎖。 Iterator
被建立之後會建立一個指向原來物件的單鏈索引表,當原來的物件數量發生變化時,這個索引表的內容不會同步改變,所以當索引指標往後移動的時候就找不到要迭代的物件,所以按照 fail-fast 原則 Iterator 會馬上丟擲 java.util.ConcurrentModificationException 異常。
所以 Iterator 在工作的時候是不允許被迭代的物件被改變的。但你可以使用 Iterator 本身的方法 remove() 來刪除物件, Iterator.remove() 方法會在刪除當前迭代物件的同時維護索引的一致性