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);
}
}
}
```
### 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);
}
}
}
```