Iterator 模式

Iterator 模式

做用

迭代器模式提供一種方法順序訪問一個集合對象中的各個元素,而又不暴露其內部的表示。java

定義

在面向對象的編程中,迭代器模式是一種設計模式,其中迭代器用於遍歷容器並訪問容器的元素。迭代器模式將算法與容器分離。算法

有許多種方法能夠把對象組成一個集合。底層的容器能夠是數組,堆棧,鏈表,散列表等。當用戶但願遍歷這些對象的時候,咱們不但願用戶須要看到底層容器的實現。迭代器模式就是用來處理這種狀況的。編程

看看實現

public interface Iterable<T> {
    /** * Returns an iterator over elements of type {@code T}. * * @return an Iterator. */
    Iterator<T> iterator();
}

public interface Collection<E> extends Iterable<E> {
	
    ...

		/** * Returns an iterator over the elements in this collection. There are no * guarantees concerning the order in which the elements are returned * (unless this collection is an instance of some class that provides a * guarantee). * * @return an <tt>Iterator</tt> over the elements in this collection */
    Iterator<E> iterator();

    ...
}

public interface Iterator<E> {
    /** * Returns {@code true} if the iteration has more elements. * (In other words, returns {@code true} if {@link #next} would * return an element rather than throwing an exception.) * * @return {@code true} if the iteration has more elements */
    boolean hasNext();

    /** * Returns the next element in the iteration. * * @return the next element in the iteration * @throws NoSuchElementException if the iteration has no more elements */
    E next();

    /** * Removes from the underlying collection the last element returned * by this iterator (optional operation). This method can be called * only once per call to {@link #next}. The behavior of an iterator * is unspecified if the underlying collection is modified while the * iteration is in progress in any way other than by calling this * method. * * @implSpec * The default implementation throws an instance of * {@link UnsupportedOperationException} and performs no other action. * * @throws UnsupportedOperationException if the {@code remove} * operation is not supported by this iterator * * @throws IllegalStateException if the {@code next} method has not * yet been called, or the {@code remove} method has already * been called after the last call to the {@code next} * method */
    default void remove() {
        throw new UnsupportedOperationException("remove");
    }

    /** * Performs the given action for each remaining element until all elements * have been processed or the action throws an exception. Actions are * performed in the order of iteration, if that order is specified. * Exceptions thrown by the action are relayed to the caller. * * @implSpec * <p>The default implementation behaves as if: * <pre>{@code * while (hasNext()) * action.accept(next()); * }</pre> * * @param action The action to be performed for each element * @throws NullPointerException if the specified action is null * @since 1.8 */
    default void forEachRemaining(Consumer<? super E> action) {
        Objects.requireNonNull(action);
        while (hasNext())
            action.accept(next());
    }
}
複製代碼

Iterator 接口中,最重要的就是 nexthasNext 兩個方法了。實現了這兩個方法,就能夠完成遍歷一個集合的操做。設計模式

for/in 語句

在 Java 中,咱們常常能夠看到這樣的語句數組

List<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
list.add(3);
for (int item : list) {
    System.out.println(item);
}
複製代碼

在 Kotlin 中,也有這樣的語句數據結構

for (item in listOf(1, 2, 3)) {
    println(item)
}
複製代碼

實際上這些語句依賴了迭代器模式,在編譯成字節碼的時候,語句會被替換成如下語句的等同實現多線程

Iterator var4 = CollectionsKt.listOf(new Integer[]{1, 2, 3}).iterator();

while(var4.hasNext()) {
   int a = ((Number)var4.next()).intValue();
   System.out.println(a);
}
複製代碼

正是迭代器將集合底層的具體實現作了屏蔽,咱們在上層使用的時候纔能有如此絲滑的體驗:對於任何類型的集合,均可以用一樣的方式實現其遍歷。less

要點

迭代器容許訪問集合的元素,而不須要暴露它的內部結構ide

迭代器提供了一個通用的接口,讓咱們遍歷集合的項ui

迭代器將遍歷集合的工做封裝進一個對象中

  • 好比 java.util.LinkedList 的 Iterator 的實現類是其內部類 java.util.LinkedList.ListItr 。這個類是 private 的,實現了 Iterator 接口,這個類的實現並不會對外暴露。

咱們應該努力讓一個類只分配一個責任

迭代器意味着沒有次序,只是取出全部的元素,並不表示取出元素的前後就表明元素的大小次序。對於迭代器來講,數據結構能夠是有次序的,或是沒有次序的,,甚至數據是能夠重複的。除非某個集合的文件有特別說明,不然不能夠對迭代器取出的元素大小順序做出假設。

多線程的狀況

先了解一個概念:

Fail-fast

In systems design, a fail-fast system is one which immediately reports at its interface any condition that is likely to indicate a failure. Fail-fast systems are usually designed to stop normal operation rather than attempt to continue a possibly flawed process.

舉個🌰️

我有一個 LinkedList

1️⃣➡️️2️⃣➡️️3️⃣➡️️4️⃣➡️️5️⃣➡️️6️⃣

有兩個線程同時對這個鏈表操做。咱們將其稱之爲讀線程和寫線程。

讀線程在使用 Iterator 遍歷這個鏈表,假設當前在 3️⃣ 這個位置上,即 hasNext返回 true, next 返回 4️⃣。

寫線程對 2️⃣ 作了修改,將這個 Node 移除掉了。

那麼對於讀線程來講,迭代器會正常繼續向後遍歷這個鏈表,至關於這個鏈表沒有發生過修改。

對於寫線程來講,鏈表已經變成了 1️⃣➡️️3️⃣➡️️4️⃣➡️️5️⃣➡️️6️⃣。

那麼對於某些任務,迭代器讀取到 2️⃣ 可能就是錯誤的操做,2️⃣ 已經再也不屬於這個鏈表。

對於一個可能已經出現錯誤的操做,按照 Fail-fast 的原則,咱們應該儘早上報這個錯誤。

爲了發現這種狀況。 Java 在 AbstractList 這個類裏面添加了一個成員變量 modCount ,每當經過 List 提供的接口對其進行修改時,modCount++

protected transient int modCount = 0;
複製代碼

LinkedList 繼承自 AbstractList

建立 java.util.LinkedList.ListItr 對象的時候,會記錄當時鏈表的 modCount ,在每次操做 鏈表的時候,會先驗證當前鏈表的 modCount 是否和迭代器中記錄的一致。若是不一致,說明鏈表已經發生改變,沒法保證迭代器能返回正確的結果。因此咱們就看到了這樣的錯誤 throw new ConcurrentModificationException();

注意:這種方式只能發現經過 List 提供的接口對其內容做出的修改。好比上面的例子中,咱們持有 Node 2️⃣ ,直接修改其指向下一個節點的指針,將其設爲 Null,迭代器是不會發現這個狀況的。

順便提一嘴,ListIterator 繼承自 Iterator 並添加了 addremove 方法,他是怎麼實如今迭代的過程當中也能夠修改 List 的內容的呢?

public interface ListIterator<E> extends Iterator<E> {

	...

	void add(E e);
  
	void remove();

	...
}
複製代碼

既然修改 List 內容是經過迭代器進行的,迭代器已經知曉了 List 內容發生了什麼變化,不會出現上面的 List 內容被修改了,迭代器不知道的狀況,那麼妥善應對了 List 的變化後(這須要編寫 Iterator 的開發者來實現,保證 Iterator 訪問到的依然是正確的 List 內容),將 Iterator 中記錄的 expectedModCount 修改成當前 List 的 modCount 便可,

Refs

Head First 設計模式

en.wikipedia.org/wiki/Fail-f…


By Eric

相關文章
相關標籤/搜索