Java迭代器

# 第12章 迭代器

## 本章學(xué)習(xí)目標(biāo)

- 理解foreach循環(huán)語法糖的概念
- 掌握使用Iterator迭代器遍歷集合
- 理解Iterator迭代器的快速失敗機(jī)制
- 掌握列表迭代器ListIterator的使用

## 12.1 foreach循環(huán)

foreach循環(huán)是增強(qiáng)for循環(huán)。foreach循環(huán)的語法格式:

```java
for(元素類型 元素名 : 集合名等){
}
//這里元素名就是一個(gè)臨時(shí)變量,自己命名就可以
```
```java
public class TestForeach {
   @Test
   public void test01(){
       Collection<String> coll = new ArrayList<>();
       coll.add("小李廣");
       coll.add("掃地僧");
       coll.add("石破天");
       for (String o : coll) {
           System.out.println(o);
       }
   }
}
```

foreach循環(huán)只是一種語法糖,foreach循環(huán)可以用來遍歷數(shù)組與Collection集合。

- 當(dāng)foreach循環(huán)變量數(shù)組時(shí),底層使用的仍然是普通for循環(huán)。
- 當(dāng)foreach循環(huán)變量Collection集合時(shí),底層使用的是Iterator迭代器(見下一小節(jié))。

代碼示例:

```java
package com.atguigu.api;
public class TestForeach {
   public static void main(String[] args) {
       int[] nums = {1,2,3,4,5};
       for (int num : nums) {
           System.out.println(num);
       }
       System.out.println("-----------------");
       String[] names = {"張三","李四","王五"};
       for (String name : names) {
           System.out.println(name);
       }
   }
}
```

普通for循環(huán)與增強(qiáng)for循環(huán)的區(qū)別:

- 普通for循環(huán)可以用來重復(fù)執(zhí)行某些語句,而增強(qiáng)for循環(huán)只能用來遍歷數(shù)組或Collection集合
- 普通for循環(huán)可以用來遍歷數(shù)組或者List集合,且必須指定下標(biāo)信息。而增強(qiáng)for循環(huán)在遍歷數(shù)組或Collection集合時(shí),不用也不能指定下標(biāo)信息。
- 普通for循環(huán)在遍歷數(shù)組或List集合時(shí),可以替換元素,但是增強(qiáng)for循環(huán)不可以替換元素。

## 12.2 Iterator迭代器

### 12.2.1 Iterable接口

Collection<E>接口繼承了java.lang.Iterable<T>接口,凡是實(shí)現(xiàn)Iterable<T>接口的集合都可以使用foreach循環(huán)進(jìn)行遍歷。Iterable<T>接口包含:

- 抽象方法:`public Iterator iterator()`,黃永玉獲取對應(yīng)的迭代器對象,用來遍歷集合中的元素。所有Collection系列的集合都重寫了該方法,即都支持foreach循環(huán)和Iterator迭代器的遍歷方式。
- 默認(rèn)方法:`public default void forEach(Consumer<? super T> action)`,該方法是Java8引入的,通過傳入的Consumer接口的實(shí)現(xiàn)類對象,完成集合元素的迭代。

`java.util.function.Consumer`接口的抽象方法:

- void accept(T t):對元素t執(zhí)行給定的操作

```java
package com.atguigu.iter;
import org.junit.Test;
import java.util.ArrayList;
import java.util.Collection;
import java.util.function.Consumer;
public class TestForEachMethod {
   @Test
   public void test1(){
       Collection coll = new ArrayList();
       coll.add("小李廣");
       coll.add("掃地僧");
       coll.add("石破天");
       coll.forEach(new Consumer() {
           @Override
           public void accept(Object o) {
               System.out.println(o);
           }
       });
   }
}
```

 

### 12.2.2 Iterator接口

在程序開發(fā)中,經(jīng)常需要遍歷集合中的所有元素。針對這種需求,JDK專門提供了一個(gè)接口`java.util.Iterator<E>`。`Iterator<E>`接口也是Java集合中的一員,但它與`Collection<E>`、`Map<K,V>`接口有所不同,`Collection<E>`接口與`Map<K,V>`接口主要用于存儲(chǔ)元素,而`Iterator<E>`主要用于迭代訪問(即遍歷)`Collection<E>`中的元素,因此`Iterator<E>`對象也被稱為迭代器。

* **迭代**:即Collection集合元素的通用獲取方式。在取元素之前先要判斷集合中有沒有元素,如果有,就把這個(gè)元素取出來。繼續(xù)再判斷,如果還有就再取出來,直到把集合中的所有元素全部取出。這種獲取元素的方式,專業(yè)術(shù)語稱為迭代。

Iterator接口的常用方法如下:

* `public E next()`:返回迭代的下一個(gè)元素。
* `public boolean hasNext()`:如果仍有元素可以迭代,則返回 true。

接下來我們通過案例學(xué)習(xí)如何使用Iterator迭代集合中元素:


package com.atguigu.iterator;
import org.junit.Test;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
public class TestIterator {
   @Test
   public void test01(){
       Collection<String> coll = new ArrayList<>();
       coll.add("小李廣");
       coll.add("掃地僧");
       coll.add("石破天");
       Iterator<String> iterator = coll.iterator();
       System.out.println(iterator.next());
       System.out.println(iterator.next());
       System.out.println(iterator.next());
       System.out.println(iterator.next());
   }
   @Test
   public void test02(){
       Collection<String> coll = new ArrayList<>();
       coll.add("小李廣");
       coll.add("掃地僧");
       coll.add("石破天");
       Iterator<String> iterator = coll.iterator();//獲取迭代器對象
       while(iterator.hasNext()) {//判斷是否還有元素可迭代
           System.out.println(iterator.next());//取出下一個(gè)元素
       }
   }
}

> 提示:在進(jìn)行集合元素取出時(shí),如果集合中已經(jīng)沒有元素了,還繼續(xù)使用迭代器的next方法,將會(huì)發(fā)生java.util.NoSuchElementException沒有集合元素的錯(cuò)誤。

對于集合類型來說,foreach循環(huán)其實(shí)就是使用Iterator迭代器來完成元素的遍歷的。

```java
package com.atguigu.iterator;
import org.junit.Test;
import java.util.ArrayList;
import java.util.Collection;
public class TestForeach {
   @Test
   public void test01(){
       Collection<String> coll = new ArrayList<>();
       coll.add("小李廣");
       coll.add("掃地僧");
       coll.add("石破天");
       for (String o : coll) {
           System.out.println(o);
       }
   }
}
```

image-20220128010114124.png

 

### 12.2.3 迭代器的實(shí)現(xiàn)原理

我們在之前案例已經(jīng)完成了Iterator遍歷集合的整個(gè)過程。當(dāng)遍歷集合時(shí),首先通過調(diào)用集合的iterator()方法獲得迭代器對象,然后使用hashNext()方法判斷集合中是否存在下一個(gè)元素,如果存在,則調(diào)用next()方法將元素取出,否則說明已到達(dá)了集合末尾,停止遍歷元素。

Iterator迭代器對象在遍歷集合時(shí),內(nèi)部采用指針的方式來跟蹤集合中的元素,為了讓初學(xué)者能更好地理解迭代器的工作原理,接下來通過一個(gè)圖例來演示Iterator對象迭代元素的過程:

![](images/迭代器原理圖.bmp)

在調(diào)用Iterator的next方法之前,迭代器指向第一個(gè)元素,當(dāng)?shù)谝淮握{(diào)用迭代器的next方法時(shí),返回第一個(gè)元素,然后迭代器的索引會(huì)向后移動(dòng)一位,指向第二個(gè)元素,當(dāng)再次調(diào)用next方法時(shí),返回第二個(gè)元素,然后迭代器的索引會(huì)再向后移動(dòng)一位,指向第三個(gè)元素,依此類推,直到hasNext方法返回false,表示到達(dá)了集合的末尾,終止對元素的遍歷。

 

### 12.2.4 使用Iterator迭代器刪除元素

`java.util.Iterator<T>`迭代器接口中還有一個(gè)默認(rèn)方法:void remove() ;

那么,既然Collection已經(jīng)有remove(xx)方法了,為什么Iterator迭代器還要提供刪除方法呢?

因?yàn)樵贘DK1.8之前Collection接口沒有removeIf方法,即無法根據(jù)條件刪除。

例如:要?jiǎng)h除以下集合元素中的偶數(shù)

```java
package com.atguigu.iterator;
import org.junit.Test;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
public class TestIteratorRemove {
   @Test
   public void test01(){
       Collection<Integer> coll = new ArrayList<>();
       coll.add(1);
       coll.add(2);
       coll.add(3);
       coll.add(4);
//        coll.remove(?)//沒有removeIf方法無法實(shí)現(xiàn)刪除“偶數(shù)”
       Iterator<Integer> iterator = coll.iterator();
       while(iterator.hasNext()){
           Integer element = iterator.next();
           if(element%2 == 0){
               iterator.remove();
           }
       }
       System.out.println(coll);
   }
}
```

### 12.2.5 Iterator迭代器的快速失敗機(jī)制

如果在`Iterator`迭代器創(chuàng)建后的任意時(shí)間從結(jié)構(gòu)上修改了集合(通過迭代器自身的 remove 或 add 方法之外的任何其他方式),則迭代器將拋出 `ConcurrentModificationException`。因此,面對并發(fā)的修改,迭代器很快就完全失敗,而不是冒著在將來不確定的時(shí)間任意發(fā)生不確定行為的風(fēng)險(xiǎn)。

這樣設(shè)計(jì)是因?yàn)?,迭代器代表集合中某個(gè)元素的位置,內(nèi)部會(huì)存儲(chǔ)某些能夠代表該位置的信息。當(dāng)集合發(fā)生改變時(shí),該信息的含義可能會(huì)發(fā)生變化,這時(shí)操作迭代器就可能會(huì)造成不可預(yù)料的事情。因此,果斷拋異常阻止,是最好的方法。這就是Iterator迭代器的快速失?。╢ail-fast)機(jī)制。

注意,迭代器的快速失敗行為不能得到保證,迭代器只是盡最大努力地拋出 `ConcurrentModificationException`。因此,編寫依賴于此異常的程序的方式是錯(cuò)誤的,正確做法是:*迭代器的快速失敗行為應(yīng)該僅用于檢測 bug。

```java
package com.atguigu.iter;
import org.junit.Test;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
public class TestRemoveElement {
   @Test
   public void test1(){
       Collection other = new ArrayList();
       other.add("atguigu");
       other.add("hello");
       other.add("java");
       //刪除包含a字母的元素
       Iterator iterator = other.iterator();
       while(iterator.hasNext()){
           Object next = iterator.next();//取出元素
           String s = (String) next;//向下轉(zhuǎn)型
           if(s.contains("a")){
               //刪除s
               other.remove(s);
           }
       }
       System.out.println(other);//ConcurrentModificationException
   }
   @Test
   public void test2(){
       Collection other = new ArrayList();
       other.add("hello");
       other.add("atguigu");
       other.add("java");
       //刪除包含a字母的元素
       Iterator iterator = other.iterator();
       while(iterator.hasNext()){
           Object next = iterator.next();//取出元素
           String s = (String) next;//向下轉(zhuǎn)型
           if(s.contains("a")){
               //刪除s
               other.remove(s);
           }
       }
       System.out.println(other);//[hello, java]
   }
   @Test
   public void test3(){
       //現(xiàn)在不鼓勵(lì)大家用這種方式刪除了,但是如果你要用,要注意寫法
       //現(xiàn)在推薦用removeIf方法
       Collection other = new ArrayList();
       other.add("hello");
       other.add("atguigu");
       other.add("java");
       //刪除包含a字母的元素
       Iterator iterator = other.iterator();
       while(iterator.hasNext()){
           Object next = iterator.next();//取出元素
           String s = (String) next;//向下轉(zhuǎn)型
           if(s.contains("a")){
               //刪除s
//                other.remove(s);//錯(cuò)誤的
               iterator.remove();//改為迭代器的刪除方法
           }
       }
       System.out.println(other);//[hello, java]
   }
}
```

那么迭代器如何實(shí)現(xiàn)快速失?。╢ail-fast)機(jī)制的呢?

* 在ArrayList等集合類中都有一個(gè)modCount變量。它用來記錄集合的結(jié)構(gòu)被修改的次數(shù)。
* 當(dāng)我們給集合添加和刪除操作時(shí),會(huì)導(dǎo)致modCount++。
* 然后當(dāng)我們用Iterator迭代器遍歷集合時(shí),創(chuàng)建集合迭代器的對象時(shí),用一個(gè)變量記錄當(dāng)前集合的modCount。例如:`int expectedModCount = modCount;`,并且在迭代器每次next()迭代元素時(shí),都要檢查 `expectedModCount != modCount`,如果不相等了,那么說明你調(diào)用了Iterator迭代器以外的Collection的add,remove等方法,修改了集合的結(jié)構(gòu),使得modCount++,值變了,就會(huì)拋出ConcurrentModificationException。

下面以AbstractList<E>和ArrayList.Itr迭代器為例進(jìn)行源碼分析:

AbstractList<E>類中聲明了modCount變量,modCount是這個(gè)list被結(jié)構(gòu)性修改的次數(shù)。子類使用這個(gè)字段是可選的,如果子類希望提供fail-fast迭代器,它僅僅需要在add(int, E),remove(int)方法(或者它重寫的其他任何會(huì)結(jié)構(gòu)性修改這個(gè)列表的方法)中添加這個(gè)字段。調(diào)用一次add(int,E)或者remove(int)方法時(shí)必須且僅僅給這個(gè)字段加1,否則迭代器會(huì)拋出偽裝的ConcurrentModificationExceptions錯(cuò)誤。如果一個(gè)實(shí)現(xiàn)類不希望提供fail-fast迭代器,則可以忽略這個(gè)字段。

Arraylist的Itr迭代器:

```java
  private class Itr implements Iterator<E> {
       int cursor;      
       int lastRet = -1; 
       int expectedModCount = modCount;//在創(chuàng)建迭代器時(shí),expectedModCount初始化為當(dāng)前集合的modCount的值
       public boolean hasNext() {
           return cursor != size;
       }
       @SuppressWarnings("unchecked")
       public E next() {
           checkForComodification();//校驗(yàn)expectedModCount與modCount是否相等
           int i = cursor;
           if (i >= size)
               throw new NoSuchElementException();
           Object[] elementData = ArrayList.this.elementData;
           if (i >= elementData.length)
               throw new ConcurrentModificationException();
           cursor = i + 1;
           return (E) elementData[lastRet = i];
       }
          final void checkForComodification() {
           if (modCount != expectedModCount)//校驗(yàn)expectedModCount與modCount是否相等
               throw new ConcurrentModificationException();//不相等,拋異常
       }
}
```

ArrayList的remove方法:

```java
   public boolean remove(Object o) {
       if (o == null) {
           for (int index = 0; index < size; index++)
               if (elementData[index] == null) {
                   fastRemove(index);
                   return true;
               }
       } else {
           for (int index = 0; index < size; index++)
               if (o.equals(elementData[index])) {
                   fastRemove(index);
                   return true;
               }
       }
       return false;
   }

   private void fastRemove(int index) {
       modCount++;
       int numMoved = size - index - 1;
       if (numMoved > 0)
           System.arraycopy(elementData, index+1, elementData, index,
                            numMoved);
       elementData[--size] = null; // clear to let GC do its work
   }
```

## 12.3 列表專用迭代器ListIterator

List 集合額外提供了一個(gè) listIterator() 方法,該方法返回一個(gè) ListIterator 列表迭代器對象, ListIterator 接口繼承了 Iterator 接口,提供了專門操作 List 的方法:

* void add():通過迭代器添加元素到對應(yīng)集合
* void set(Object obj):通過迭代器替換正迭代的元素
* void remove():通過迭代器刪除剛迭代的元素
* boolean hasPrevious():如果以逆向遍歷列表,往前是否還有元素。
* Object previous():返回列表中的前一個(gè)元素。
* int previousIndex():返回列表中的前一個(gè)元素的索引
* boolean hasNext()
* Object next()
* int nextIndex()

```java
package com.atguigu.list;
import org.junit.Test;
import java.util.ArrayList;
import java.util.List;
import java.util.ListIterator;
public class TestListIterator {
   @Test
   public void test7() {
        /*
       ArrayList是List接口的實(shí)現(xiàn)類。
       演示ListIterator迭代器
        */
       List<String> list = new ArrayList<>();
       list.add("hello");
       list.add("java");
       list.add("world");
       list.add("mysql");
       list.add("java");
       ListIterator<String> stringListIterator = list.listIterator();
       while(stringListIterator.hasNext()){
           int index = stringListIterator.nextIndex();
           String next = stringListIterator.next();
           if(next.equals("java")){
               stringListIterator.set("JavaEE");
           }
       }
       System.out.println(list);//[hello, JavaEE, world, mysql, JavaEE]
   }
   @Test
   public void test6() {
        /*
       ArrayList是List接口的實(shí)現(xiàn)類。
       演示ListIterator迭代器
        */
       List<String> list = new ArrayList<>();
       list.add("hello");
       list.add("java");
       list.add("world");
       list.add("mysql");
       list.add("java");
       ListIterator<String> stringListIterator = list.listIterator();
       while(stringListIterator.hasNext()){
           int index = stringListIterator.nextIndex();
           String next = stringListIterator.next();
           System.out.println("index = " + index +",next = " + next);
       }
       System.out.println("---------------");
       while(stringListIterator.hasPrevious()){
           int index = stringListIterator.previousIndex();
           String previous = stringListIterator.previous();
           System.out.println("index = " + index +",previous = " + previous);
       }
       System.out.println("---------------");
       stringListIterator = list.listIterator(2);
       while(stringListIterator.hasNext()){
           int index = stringListIterator.nextIndex();
           String next = stringListIterator.next();
           System.out.println("index = " + index +",next = " + next);
       }
   }
}
```