詳解迭代器Iterator


一、迭代器Iterator


  集合接口Collection繼承了接口Iterable,該接口提供了一個用於獲取迭代器Iterator的方法,使用迭代器我們可以逐個訪問集合中的元素。下面是迭代器接口源碼:
1 public interface Iterator<E> {
2     
3     boolean hasNext();
4     
5     E next();
6 
7     void remove();
8 }

   需要注意的是,Map接口並未繼承Iterable,因此Map無法獲取到迭代器。

1.1.向后遍歷元素

  首先來看看兩個相關API的功能:
  • hasNext():詢問是否還有下一個元素,返回true表示有,反之表示沒有。
  • next():移動迭代器游標,跨過下一個元素並返回該元素的引用,如果不存在下一個元素,方法會拋出"NoSuchElementException"。
  所以,我們一般通過組合使用hasNext()與next()方法,逐個訪問集合中的元素,元素的訪問順序依賴於集合的具體實現,比如List是按照放入順序訪問,Set訪問順序則不一定(可能是有序的TreeSet,可能是無序的HashSet),下面是一段示例代碼:
while (iter.hasNext()) {
    String element = it.next();
    //do something with element
}

   從JavaSE5.0起,上面這個循環可以采用一種更優雅的縮寫形式,即foreach循環。編譯器在編譯時會簡單的將foreach循環翻譯成迭代器循環,因此,foreach循環支持任何實現了Iterable接口的對象。

 

1.2.刪除元素

  使用remove刪除集合中的元素:
  • remove():刪除上次調用next()方法時返回的元素。需要注意,一次next()只支持一次remove(),如果次數無法匹配,remove操作將會拋出"IllegalStateException",比如:
iter.next();
iter.remove();
iter.remove(); //拋出IllegalStateException

 

二、List迭代器


  Collection接口的子接口List提供了一個方法listIterator(),用於返回Iterator的子接口ListIterator對象。
1 ListIterator<E> listIterator();

   相比於Iterator,ListIterator添加了從后向前遍歷、獲取迭代索引以及添加、設置元素的功能。

 1 public interface ListIterator<E> extends Iterator<E> {
 2 
 3     boolean hasPrevious();
 4     
 5     E previous();
 6     
 7     int nextIndex();
 8     
 9     int previousIndex();
10     
11     void add(E e);
12     
13     void set(E e);
14 
15 }

2.1.向前遍歷元素

  使用方式類比於向后遍歷,previous()方法用於向前跨越一個元素並返回該元素的引用,hasPrevious()用於詢問前面是否還有元素。

 

2.2.獲取迭代索引

  獲取迭代索引的功能只與當前迭代器游標所處位置有關,與是否向前遍歷還是向后遍歷無關:
  • nextIndex():返回下一次調用next()方法時返回的元素的索引。
  • previousIndex():返回下一次調用previous()方法時返回的元素的索引。

 

2.3.添加元素

  add方法在迭代器當前位置之前添加一個新對象,例如下面的代碼將越過列表中的第一個元素,並在第二個元素之前添加Juliet。
1 List<String> staff = new LinkedList<>();
2 staff.add("Amy");
3 staff.add("Bob");
4 staff.add("Carl");
5 ListIterator<String> iter = staff.listIterator();
6 iter.next();
7 iter.add("Juliet");

  可以看到,Juliet成為第二個元素,而Bob和Carl的位置依次向后移動一位。

  注意,與remove方法不同的是,add方法不與next()進行綁定。也就是說,使用add沒有任何需要移動迭代器位置的前提條件,並且可以一直添加元素,在上面的示例中一次可以添加Juliet、Green、Lucy等人,列表變成如下樣子:

 

2.4.設置元素

  Set方法使用一個新元素取代調用next()或previous()方法返回的上一個元素,例如:
1 ListIterator<String> iter = list.listIterator();
2 String oldValue = iter.next();
3 iter.set(newValue);

   newValue將會替換掉oldValue這個元素。

 

三、關於ConcurrentModificationException


  有時候,在使用foreach或iterator遍歷時會拋出ConcurrentModificationException,該異常產生的原因是:迭代器發現它的集合被另一個迭代器修改了,或是被該集合自身的方法修改了。下面我們詳細解釋一下這種發現機制是如何實現的?
  集合對改寫操作維護一個計數(改寫操作指的是對集合結構性的修改,如添加、刪除元素,而修改元素內容、替換元素不被視為結構性修改),從該集合獲得的每一個迭代器與集合自身API的改寫操作都會累加這個計數。同時,每個迭代器自身也獨自維護一個計數值,每一次改寫操作也會累加自身的計數值。迭代器的每個方法開始處都會檢查自己的改寫計數值與集合的改寫計數值是否一致,如果不一致,就拋出異常ConcurrentModificationException。
  根據上述的原理,我們解釋一下如下代碼產生ConcurrentModificationException的原因:我們知道,foreach循環本質上是一個迭代器遍歷。第一次遍歷獲取到element時,迭代器自身計數和集合計數均為0,在循環體中調用了集合自身方法刪除元素后,集合計數被修改為1。在下一次執行迭代器方法hasNext()時(編譯器編譯結果),迭代器發現自身計數與集合計數不一致,拋出異常。不過,需要值得注意的細節是,第一次remove是執行成功的,首元素已被刪掉,在實際開發中一定要注意這個問題。
1 List<String> list = new ArrayList<>();
2 list.add("a");
3 list.add("a");
4 list.add("a");
5 for (String element : list) {
6     list.remove(element);
7 }

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM