Java 迭代器介紹
迭代器模式
迭代器模式是一個典型的設計模式,提供一種方法訪問一個容器對象中各個元素,而又不暴露該對象的內部細節。因為屏蔽了細節,可以針對不同實現的容器,提供一致的標準化的訪問方法。
迭代器模式有四個部分組成:
- 抽象容器 Aggregate
- 具體容器 ConcreteAggregate
- 抽象迭代器 Iterator
- 迭代器實現 ConcreteIterator
Java 迭代器
框架
Java 中的容器接口 Collection 繼承了迭代器接口 Iterable,迭代器接口中的 iterator() 方法返回 Iterator 類型對象。這里 Collection 相當于迭代器模式中的抽象容器 Aggregate;Iterator 相當于迭代器模式中的抽象迭代器 Iterator。
public interface Collection<E> extends Iterable<E> {
Iterator<E> iterator();
// 省略其他
}
public interface Iterable<T> {
Iterator<T> iterator();
}
public interface Iterator<E> {
// 判斷是否還有下一個元素
boolean hasNext();
// 獲取下一個元素
E next();
// 移除一個元素
default void remove() {
throw new UnsupportedOperationException("remove");
}
}
java 中 List 、Set、Queue 容器都會繼承實現 Collection 類,提供迭代器接口,我們以 List 為例看一下迭代器的實現。
List 的迭代器實現
我們可以看一下 ArrayList 中迭代器的實現:
- Itr 實現了 hasNext、next、remove 三個方法
- 遍歷靠下標 cursor 的遞增,其實現和我們自己遍歷數組一樣(迭代器好處是屏蔽細節、標準化)
- remove 之后 lastRet 置為 -1,調用一次 next 后只能調用一次 remove,否則會拋異常
- 調用 next 之前也要調用 hasNext 判斷,否則 cursor 大于等于 size 會拋異常
- 迭代器遍歷過程中,只能使用其 remove 方法移除元素,如果使用了 List 本身的 add、remove 方法,會導致下次調用 next 方法時拋出異常。原因是迭代器 Itr 里保存了 List 修改次數 modCount 的值到局部變量 expectedModCount 里,不通過迭代器的修改會導致兩者不一致,導致 checkForComodification 方法拋異常。
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
public Iterator<E> iterator() {
return new Itr();
}
/**
* An optimized version of AbstractList.Itr
*/
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
int expectedModCount = modCount;
public boolean hasNext() {
return cursor != size;
}
@SuppressWarnings("unchecked")
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];
}
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();
}
}
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}
}
ArrayList 中的迭代器在容器變更后會失效,那么有沒有不失效的呢?我們可以看看 CopyOnWriteArrayList 的迭代器實現:
public class CopyOnWriteArrayList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable {
public Iterator<E> iterator() {
return new COWIterator<E>(getArray(), 0);
}
private static class COWIterator<E> implements ListIterator<E> {
/** Snapshot of the array **/
private final Object[] snapshot;
/** Index of element to be returned by subsequent call to next. */
private int cursor;
private COWIterator(Object[] elements, int initialCursor) {
cursor = initialCursor;
snapshot = elements;
}
public boolean hasNext() {
return cursor < snapshot.length;
}
public E next() {
if (! hasNext())
throw new NoSuchElementException();
return (E) snapshot[cursor++];
}
}
}
CopyOnWriteArrayList 迭代器 COWIterator 遍歷的是創建迭代器時的數據快照,這樣后續容器的元素增加和減少,就不會導致迭代器的失效了。請注意這里并沒有記錄容器元素的實際大小,容器元素數目完全取決于數組 snapshot 的長度。我們知道在 ArrayList 中,為了減少擴容次數,擴容時數組長度會比實際所需的長,導致數組長度和容器元素個數不一致。CopyOnWriteArrayList 沒有這個問題嗎?確實是的,我們看一下其添加元素時,擴容的源代碼:
- 每次擴容,新數組長度等于老數組長度加1,長度剛剛好。
- 每次添加元素,都需要擴容,效率低下。
public boolean add(E e) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] elements = getArray();
int len = elements.length;
Object[] newElements = Arrays.copyOf(elements, len + 1);
newElements[len] = e;
setArray(newElements);
return true;
} finally {
lock.unlock();
}
}
擴展迭代器 ListIterator
List 容器中,不僅提供了普通迭代器 Iterator 的實現,還有一個功能更強的迭代器 ListIterator:
- 除了正向遍歷之外,還提供了反向遍歷的功能
- 除了 remove 之外,提供了 set 和 add,當然通過迭代器的這些方法修改數據,都不應該導致迭代器失效
public interface ListIterator<E> extends Iterator<E> {
boolean hasNext();
E next();
boolean hasPrevious();
E previous();
int nextIndex();
int previousIndex();
void remove();
void set(E e);
void add(E e);
}
總結
迭代器模式,體現了標準化遍歷容器,屏蔽內部實現細節的編程思想。
普通迭代器在遇到容器修改時會失效,但是一些線程安全類實現的迭代器比如 COWIterator 是不會因容器修改而失效的。