Skip to content

Latest commit

 

History

History
275 lines (186 loc) · 8.61 KB

java.util.Iterator.adoc

File metadata and controls

275 lines (186 loc) · 8.61 KB

迭代器 Iterator、 Enumeration、 Spliterator 与 Iterable

  1. java.util.Iterator

  2. java.util.PrimitiveIterator

  3. java.util.ListIterator

  4. java.util.Spliterator

  5. java.util.Enumeration

  6. java.lang.Iterable

迭代器模式

在进行代码分析之前,D瓜哥想先来讲解一下设计模式。然后结合 Java 中 IteratorIteratable ,具体分析一下迭代器在 Java 中的实现。

迭代器模式(Iterator)

提供一种方法顺序访问一个聚合对象中各个元素,而不是暴露该对象的内部表示。

— Erich Gamma、Richard Helm、Ralph Johnson、John Vlissides
《设计模式》

类图如下:

@startuml
'skinparam nodesep 70
skinparam titleFontSize 30
skinparam defaultFontName Hiragino Sans GB

title **迭代器模式**

abstract class Iterator {
+ {abstract} first() :Object
+ {abstract} next() :Object
+ {abstract} isDone() :boolean
+ {abstract} currentItem() :Object
}
note top: 迭代抽象类,用于定义得到开始对象、\n得到下一个对象、判断是否到结尾、\n当前对象等抽象方法,统一接口。

class ConcreteIterator {
}
note bottom: 具体迭代器类,继承 Iterator,\n实现开始、下一个、是否结尾、\n当前对象等方法。

abstract class Aggregate {
+ {abstract} createIterator() :Iterator
}
note top: 聚集抽象类

class ConcreteAggregate {
+ createIterator() :Iterator
}
note bottom: 具体聚集类,继承 Aggregate。

class Client {
}

Client -left-> Aggregate
Client -right-> Iterator

Aggregate <|-- ConcreteAggregate
Iterator <|-- ConcreteIterator

ConcreteIterator -left-> ConcreteAggregate
ConcreteIterator <.. ConcreteAggregate

skinparam footerFontSize 20
footer D瓜哥 · https://www.diguage.com · 出品

@enduml

当需要访问一个聚集对象,而且不管这些对象是什么都需要遍历的时候,就应该考虑用迭代器模式。

当需要对聚集有多种方式遍历时,可以考虑用迭代器模式。

为遍历不同的聚集结构提供如开始、下一个、是否结束、当前哪一项等统一的接口。

尽管我们不需要显式的引用迭代器,但系统本身还是通过迭代器来实现遍历的。总地来说,迭代器(Iterator)模式就是分离了集合对象的遍历行为,抽象出一个迭代器类来负责,这样既可以做到不暴露集合的内部结构,又可让外部代码透明地访问集合内部的数据。

请问: Java 中是如何应用迭代器模式呢?

Iterator

从上面的设计模式可以看出,迭代器模式就是为了遍历不同的聚集结构提供诸如开始、下一个、是否结束、当前元素等常见操作的统一接口。来看看 Java 集合类是如何提炼接口的。

java.util.Iterator
public interface Iterator<E> {

    boolean hasNext();

    E next();

    default void remove() {
        throw new UnsupportedOperationException("remove");
    }

    /**
     * @since 1.8
     */
    default void forEachRemaining(Consumer<? super E> action) {
        Objects.requireNonNull(action);
        while (hasNext())
            action.accept(next());
    }
}

从上述代码中,可以看出 Java 提取了 boolean hasNext()E next()void remove() 等三个操作方法;在 Java 8 中,为了支持 Stream API,有增加了 void forEachRemaining(Consumer<? super E> action) 方法。

这里多扯一句,Java 在 1.2 以前迭代器是通过另外一个接口实现的:

java.util.Enumeration
public interface Enumeration<E> {

    boolean hasMoreElements();

    E nextElement();
}

与上面的 java.util.Iterator 对比可以看出,两者差别不大。那为什么 Java 在已有 java.util.Iterator 接口的情况下,还要推出 java.util.Enumeration 接口呢?在 java.util.Iterator 接口的 JavaDoc 中给出了如下理由:

  • Iterators allow the caller to remove elements from the underlying collection during the iteration with well-defined semantics.

  • Method names have been improved.

我们都知道,在 Java 8 之前,接口中的方法不能有任何实现。所以,为了保持兼容性,不能在已有接口中增加方法。只能另起炉灶,把“洞”补上。这也就不难理解,为什么又搞出了个 java.util.Iterator

这里再多提一句,需要增加自定义的迭代器实现时,请优先选择 java.util.Iterator

请问:既然有迭代器接口定义了,那么 Java 又是如何生成迭代器实例呢?

Iterable

既然迭代器可以抽象成一个公共的接口,那么生成迭代器实例的这个操作,也可以抽象成一个接口。 Java 也确实是这样做的:

java.lang.Iterable
public interface Iterable<T> {

    Iterator<T> iterator();

    /**
     * @since 1.8
     */
    default void forEach(Consumer<? super T> action) {
        Objects.requireNonNull(action);
        for (T t : this) {
            action.accept(t);
        }
    }

    /**
     * @since 1.8
     */
    default Spliterator<T> spliterator() {
        return Spliterators.spliteratorUnknownSize(iterator(), 0);
    }
}

从类的定义中,可以看到 java.lang.Iterable 提供了 iterator(),用于创建 java.util.Iterator 示例对象。

在 Java 8 中,为了支持 Lambda 表达式和 Stream API,又增加了 forEach(Consumer<? super T> action)spliterator() 方法。

在思考实现原理的过程中,D瓜哥突然想到,java.lang.Iterable 就是一个工厂方法模式的应用。来分析一下:

工厂方法模式

先来看看工厂方法模式的定义:

工厂方法模式(Factory Method)

定义一个用于创建对象的接口,让子类决定实例化哪一个类。工厂方法使一个类的实例化延迟到其子类。

— Erich Gamma、Richard Helm、Ralph Johnson、John Vlissides
《设计模式》

类图如下:

@startuml
skinparam defaultFontName Hiragino Sans GB

title <b>工厂方法模式</b>

abstract class Product {
}
note top: 定义工厂方法所创建的对象的接口。

class ConcreteProduct {
}
note bottom: 具体的产品,实现了 Product 接口。

abstract class Factory {
  + {abstract} factoryMethod() :Product
}
note top: 声明工厂方法, 该方法返回一个 Product 类型的对象。

class ConcreteFactory {
}
note bottom: 重定义工厂方法以返回一个 ConcreteProduct 实例。

Product <|-- ConcreteProduct
Factory <|-- ConcreteFactory
ConcreteFactory -right-> ConcreteProduct

@enduml
  • java.lang.Iterable 就相当于 Factory 接口,也就是工厂;

  • java.util.Iterator 就相当于工厂生成的产品 Product

  • iterator() 方法就是工厂方法 factoryMethod()

  • java.lang.Iterablejava.util.Iterator 子类,都放在了各个集合类中来具体实现。

在各个聚集类中,去实现 java.lang.Iterable 接口,然后根据聚集类的情况,返回对应的 java.util.Iterator 具体类对象即可。

细心的童鞋,可能发现还有个类似迭代器的类 Spliterator。这是个什么类?为啥要增加相关的接口呢?

Spliterator

ListIterator

java.util.Iterator 是针对整个集合类抽象出来的通用迭代器。但是,可以思考一下,对于 java.util.List 是不是可以有更契合的迭代器?

关于这个问题的答案,JDK 给出了自己的答案:

public interface ListIterator<E> extends Iterator<E> {
    // Query Operations

    boolean hasNext();

    E next();

    boolean hasPrevious();

    E previous();

    int nextIndex();

    int previousIndex();


    // Modification Operations

    void remove();

    void set(E e);

    void add(E e);
}

由于 List 是有序的,从代码中可以看出,所以,ListIteratorIterator 基础之上,增加了获前后元素相关的方法;同时,还增加了修改相关的操作方法。

因为增加了 hasPrevious()previous(),那么 ListIterator 就有了双向遍历的能力:既可以像传统迭代器那样,从前向后遍历;又可以逆向,从后想前遍历。这样在某些场景下就会特别方便。