-
java.util.Iterator
-
java.util.PrimitiveIterator
-
java.util.ListIterator
-
java.util.Spliterator
-
java.util.Enumeration
-
java.lang.Iterable
在进行代码分析之前,D瓜哥想先来讲解一下设计模式。然后结合 Java 中 Iterator
和 Iteratable
,具体分析一下迭代器在 Java 中的实现。
- 迭代器模式(Iterator)
提供一种方法顺序访问一个聚合对象中各个元素,而不是暴露该对象的内部表示。
《设计模式》
类图如下:
@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 中是如何应用迭代器模式呢?
从上面的设计模式可以看出,迭代器模式就是为了遍历不同的聚集结构提供诸如开始、下一个、是否结束、当前元素等常见操作的统一接口。来看看 Java 集合类是如何提炼接口的。
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 以前迭代器是通过另外一个接口实现的:
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 又是如何生成迭代器实例呢?
既然迭代器可以抽象成一个公共的接口,那么生成迭代器实例的这个操作,也可以抽象成一个接口。 Java 也确实是这样做的:
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)
定义一个用于创建对象的接口,让子类决定实例化哪一个类。工厂方法使一个类的实例化延迟到其子类。
《设计模式》
类图如下:
@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.Iterable
和java.util.Iterator
子类,都放在了各个集合类中来具体实现。
在各个聚集类中,去实现 java.lang.Iterable
接口,然后根据聚集类的情况,返回对应的 java.util.Iterator
具体类对象即可。
细心的童鞋,可能发现还有个类似迭代器的类 Spliterator
。这是个什么类?为啥要增加相关的接口呢?
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
是有序的,从代码中可以看出,所以,ListIterator
在 Iterator
基础之上,增加了获前后元素相关的方法;同时,还增加了修改相关的操作方法。
因为增加了 hasPrevious()
和 previous()
,那么 ListIterator
就有了双向遍历的能力:既可以像传统迭代器那样,从前向后遍历;又可以逆向,从后想前遍历。这样在某些场景下就会特别方便。