迭代器模式定義
就是提供一種方法對一個容器對象中的各個元素進行訪問,而又不暴露該對象容器的內部細節。這意味着迭代器需要提供統一的接口。
普通訪問
我們先來看下正常訪問集合
訪問數組
int array[] = new int[3];
for (int i = 0; i < array.length; i++) {
System.out.println(array[i]);
}
訪問List
List<String> list = new ArrayList<String>();
for(int i = 0 ; i < list.size() ; i++) {
String string = list.get(i);
}
我們可以看出,以上兩種方式,我們總是知道集合的內部結構。訪問集合元素的代碼是和集合本身緊密耦合的。無法將訪問遍歷邏輯從集合類客戶端代碼抽離出來。不同的集合會有不用的遍歷代碼。所以纔有Iterator,它總是用同一種邏輯來遍歷集合。使得客戶端自身不需要來維護集合的內部結構,所有的內部狀態都由Iterator來維護。客戶端不用直接和集合進行打交道,而是控制Iterator向它發送向前向後的指令,就可以遍歷集合。
Java迭代器
- java.util.Iterator
先看下迭代器接口的定義
package java.util;
public interface Iterator<E> {
boolean hasNext();//判斷是否存在下一個對象元素
E next();//獲取下一個元素
void remove();//移除元素
}
2.Iterable
Java中還提供了一個Iterable接口,Iterable接口實現後的功能是‘返回’一個迭代器,我們常用的實現了該接口的子接口有:Collection、List、Set等。該接口的iterator()方法返回一個標準的Iterator實現。實現Iterable接口允許對象成爲Foreach語句的目標。就可以通過foreach語句來遍歷你的底層序列。
Iterable接口包含一個能產生Iterator對象的方法,並且Iterable被foreach用來在序列中移動。因此如果創建了實現Iterable接口的類,都可以將它用於foreach中。
Package java.lang;
import java.util.Iterator;
public interface Iterable<T> {
Iterator<T> iterator();
}
Java中幾乎所有的集合都直接或間接提供了遍歷本集合的迭代器實現。其作爲內部類存在於集合類內部。下面我們看一個最簡單的迭代器實現——ArrayList的迭代器。
ArrayList迭代器
在ArrayList內部類Itr實現了Iterator接口,提供next() hasNext() remove()等方法。先看下維護的幾個變量
private class Itr implements Iterator<E> {
//下一個元素的位置下標
int cursor; // index of next element to return
//上一個元素的位置下標
int lastRet = -1; // index of last element returned; -1 if no such
//預期被修改的次數值,初始值等於modCount
//modCount是ArrayList維護的變量,當進行list.add()/remove()操作時會修改這個值
int expectedModCount = modCount;
.....
}
- next()
public E next() {
checkForComodification();
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];
}
首先進行檢查,checkForComodification()是Itr內部函數,如下
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
檢查期待修改次數和真正修改次數modCount是否相等,不相等拋出異常
接下來獲取下一個元素位置下標cursor進行判斷,邏輯很簡單不解釋。最終取出元素數據數組中相應值返回並將cursor遞增。同時將lastRet遞增
- remove()
public void remove() {
if (lastRet < 0)
throw new IllegalStateException();
checkForComodification();
try {
ArrayList.this.remove(lastRet);
cursor = lastRet;
lastRet = -1;
expectedModCount = modCount;
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
首先判斷lastRet是否小於零,小於零則拋出異常。由於初始值爲-1並且只有在next()方法中才操作lastRet變量,所以迭代時不next()而直接remove()會報錯,這點也很好理解。然後是進行checkForComodification()。接着調用集合remove()方法。並將expectedModCount重新賦值,這點很重要。這也是爲什麼通過迭代器遍歷List時,使用迭代器remove()方法刪除元素沒有問題,通過list.remove()刪除就會報錯。後邊會詳細解釋。
- 外部調用
List<String> list=new ArrayList<>();
list.add("abc");
list.add("edf");
list.add("ghi");
for(Iterator<String> it=list.iterator();it.hasNext();)
{
System.out.println(it.next());
}
關於刪除元素
有這樣一個問題,需要我們思考:在迭代時如何正確刪除元素
方式一:size()遍歷調用list.remove()刪除
list.clear();
list.add("a");
list.add("b");
list.add("b");
list.add("a");
list.add("a");
/**
* 當remove()一個元素後,後面的的元素會集體向前移動,這樣刪除掉的元素的下一個
* 元素會移動到當前位置,但此位置已經循環過了,所以會漏掉該元素的循環,log如下:
*init[a, b, b, b, a],i=0
* init[a, b, b, b, a],i=1
* after delete:[a, b, b, a]
* init[a, b, b, a],i=2
* after delete:[a, b, a]
* result:[a, b, a]
*/
for(int i=0;i<list.size();i++){
System.out.println("init"+list.toString()+",i="+i);
if(list.get(i).equals("b")){
//list.remove(i); 與list.remove(obj)同效果
list.remove("b");
System.out.println("after delete:"+list.toString());
}
}
方式二:迭代器遍歷調用list.remove()
ArrayList<Integer> list = new ArrayList<Integer>();
list.add(2);
Iterator<Integer> iterator = list.iterator();
while(iterator.hasNext()){
Integer integer = iterator.next();
if(integer==2)
list.remove(integer);
}
結果:
報ConcurrentModificationException()錯誤。因爲在list.remove()時會修改modCount()值,如下:
public E remove(int index) {
rangeCheck(index);
modCount++;
E oldValue = elementData(index);
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
return oldValue;
}
modCount就和創建Iterator()時值不相等,那麼再次調用Itr.next()時進行checkForComodification()檢查,就會報錯。可以再回頭看下next()代碼
方式三:通過Itr.remove()方式刪除
ArrayList<Integer> list = new ArrayList<Integer>();
list.add(2);
Iterator<Integer> iterator = list.iterator();
while(iterator.hasNext()){
Integer integer = iterator.next();
if(integer==2)
iterator.remove(); //注意這個地方
}
正常刪除,因爲在Itr.remove()時會更新expectedModCount值
後記
Java中的集合(Collection Map)中,每一個集合都實現了自己的迭代器,這樣外部訪問時可以採用統一的接口。而實現卻是每一個集合中不同的實現。這種模式值得我們在開發中學習。