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

相关文章
相关标签/搜索