一个已经融入 JDK 血液的设计模式;一个被JDK集合框架和流(Stream)式编程应用到极致的设计模式;一个很常见,使用率非常高,开源项目中却很少原创的设计模式;一个 码农 老吴认为没有必要提供实战案例的设计模式;JDK集合框架创始人 Google 首席 java架构师 Joshua Bloch 设计模式领域的又一贡献。
大家好, 极客 架构师——专注架构师成长,我是码农老吴。
本期是《架构师基本功之设计模式》 第15期。
在第14期,我给大家分享了,行为型设计模式中的——访问者设计模式(Visitor design pattern)。并基于该模式,实现了 网盘 系统中,高扩展性的文件统计功能模块。
本期,我将分享行为型设计模式中的,另外一个设计模式,它就是我们的主人公——迭代器设计模式( iterator design pattern)。
对于什么是“ 迭代 ”,如何使用“ 迭代器 ”,这个即使对刚刚入行的码农来说,都是一件轻而易举的事情。
对于“迭代器设计模式”,我没有采用以前的讲解方式,找一些实战项目中的案例进行讲解,因为迭代器设计模式的实战案例,实在太少了,很难找到,虽然有个别的开源项目中有,项目也比较偏。
原因就是因为,JDK集合框架对迭代器的支持,已经到了登峰造极的地步。让码农本能地认为,没有任何必要,再自定义自己的迭代器了。既然如此,我们何不就近取材,直接讲解JDK中的迭代器设计模式。
直播小调查
码农老吴,要开直播了,在启动直播之前,做个小调查,希望大家多多支持, 如果下面的选项都不合适 ,也可以评论区留言或者私信我哦。
分享思路
时光穿梭机——JDK迭代器的演化之路
JDK第一代迭代器
JDK第二代迭代器
迭代器设计模式定义解析
List集合的 Iterator 代码解析
Set,Map集合的Iterator代码解析
JDK中的双向迭代器
Guava类库超炫的迭代器工具类
JDK第三代迭代器-流(Stream)时代的迭代器
下期预告
时光穿梭机——JDK迭代器的演化之路
下面的代码,7个案例,让我们乘坐 时光穿梭机 ,体验JDK从1.0到8.0,三代迭代器的演化之路。
从demo1的 6行代码,253个字符 ,进化到demo7的 两行代码,91个字符 ,有没有给你带来强烈的视觉冲击力。
第一代迭代器:代表人物是Enumerration接口。
第二代迭代器:代表人物是Iterator接口
第三代迭代器:迭代依然在,迭代器已经消失了。没有了迭代器,取而代之的是 forEach 相关的方法,是流式编程和函数式编程。这让我想起 唐朝 诗人 崔护 的一首诗。
package com.geekarchitect.patterns.iterator.collection;
import org.slfj.Logger;
import org.slfj.LoggerFactory;
import java .util.Enumeration;
import java.util.Iterator;
import java.util.List;
import java.util.Vector;
import java.util.function.Consumer;
/**
* @author 极客架构师@吴念
* @createTime/11/11
*/
public class TimeMachine {
private static final Logger LOG = LoggerFactory.getLogger(TimeMachine.class);
public void demo1(Vector<String> geekArchitectPrograms){
LOG.info("JDK第一代迭代器 java.0");
Enumeration<String> enumeration=geekArchitectPrograms.elements();
while(enumeration.hasMoreElements()){
String programName=enumeration.nextElement();
LOG.info(programName);
}
}
public void demo(List<String> geekArchitectPrograms){
LOG.info("JDK第二代迭代器 java.2");
Iterator<String> iterator=geekArchitectPrograms.iterator();
while (iterator.hasNext()){
String programName=iterator.next();
LOG.info(programName);
}
}
public void demo(List<String> geekArchitectPrograms){
LOG.info("JDK第二代迭代器 java");
for(String programName:geekArchitectPrograms){
LOG.info(programName);
}
}
public void demo(List<String> geekArchitectPrograms){
LOG.info("JDK第三代迭代器 java 方式1");
geekArchitectPrograms.iterator().forEachRemaining(new Consumer<String>() {
@Override
public void accept(String s) {
LOG.info(s);
}
});
}
public void demo(List<String> geekArchitectPrograms){
LOG.info("JDK第三代迭代器 java 方式2");
geekArchitectPrograms.forEach(new Consumer<String>() {
@Override
public void accept(String s) {
LOG.info(s);
}
});
}
public void demo(List<String> geekArchitectPrograms){
LOG.info("JDK第三代迭代器 java 方式3");
geekArchitectPrograms.forEach(programName->LOG.info(programName));
}
public void demo(List<String> geekArchitectPrograms){
LOG.info("JDK第三代迭代器 java 方式4");
geekArchitectPrograms.stream().forEach(LOG::info);
}
}
运行结果
补充说明
demo3中java的增强for语句,下面的反编译之后代码,暴露了它的本质,它的语法比Iterator简单,但是换汤不换药,底层仍然是Iterator,它只是一颗 语法糖 而已。
源代码
反编译之后的代码
JDK第一代迭代器
Enumeration接口
JDK中的迭代器,最早的当属Enumeration接口,它是从java1.0版本开始的,它里面定义了两个方法:hasMoreElements()和nextElement(),配合这个接口的,是老一辈的集合工具类 Vector 和Hashtable。这两个类,目前大家使用的比较少,只有在面试的时候,经常会问Vector和ArrayLIst有什么区别,Hashtable和 HashMap 有什么区别,不知道答案的可以百度一下,这不是我们本次分享的重点。
当 《effective Java》 的作者 Josh Bloch 入职Sun公司,从安全组调入核心平台组之后,开始对JDK的集合进行了大刀阔斧的革命,他成为了新一代的JDK集合框架的创始人,也就是我们现在所熟悉的JDK框架集合。Josh Bloch也因此在2004年,获得了SUN公司的杰出工程师(Distinguished Engineer)称号。我的架构师书房栏目,后面将计划分享Josh Bloch的这本荣膺2002年度Jolt大奖的书《Effective Java》,感兴趣的可以评论区留言哦。
Enumeration接口,注意两点
1,看它的类注解,@since 1.0,代表着他往日的辉煌
2,asIterator()方法的注解,@since 9,预示着迭代器模式的另外一个新时代。
package java.util;
/*
*
* @param <E> the type of elements returned by this enumeration
*
* @see java.util.Iterator
* @see java.io.Sequence InputStream
* @see java.util.Enumeration#nextElement()
* @see java.util.Hashtable
* @see java.util.Hashtable#elements()
* @see java.util.Hashtable#keys()
* @see java.util.Vector
* @see java.util.Vector#elements()
*
* @author Lee Boynton
* @since.0
*/
public interface Enumeration<E> {
/**
* Tests if this enumeration contains more elements.
*
* @return {@code true} if and only if this enumeration object
* contains at least one more element to provide ;
* {@code false} otherwise.
*/
boolean hasMoreElements();
/**
* Returns the next element of this enumeration if this enumeration
* object has at least one more element to provide.
*
* @return the next element of this enumeration.
* @throws NoSuchElement exception if no more elements exist.
*/
E nextElement();
/**
*
* @return an Iterator representing the remaining elements of this Enumeration
*
* @since
*/
default Iterator<E> asIterator() {
return new Iterator<>() {
@Override public boolean hasNext() {
return hasMoreElements();
}
@Override public E next() {
return nextElement();
}
};
}
}
案例
Enumeration接口的底层实现,我们就不分析了,毕竟已经过时了,分析它的源代码价值不大,我们继续往下看。
JDK第二代迭代器
Iterator接口
Iterator接口大家都不陌生,不过大家应该好好看看这个接口的注释部分,@author 赫然写着Josh Bloch作者的名字。
另外,就是Josh Bloch本人,在这个接口的注释中,对Iterator接口的特点以及它与Enumeration接口的关系做了说明。Iterator接口就是用来取代(takes the place of)Enumeration接口的,它与Enumeration接口有两个方面的不同。
1,Iterator接口,不仅可以遍历集合,还增加 remove 方法,可以删除集合元素。
2,第二点比较逗,就是Iterator接口的方法名称,比Enumeration接口的要简洁不少。
它里面的核心方法是hasNext()和next()接口,而Enumeration接口里面的是hasMoreElements()和nextElement()。
另外,forEachRemaining方法,大家也要注意一下,它是从1.8开始的,预示着新一代的迭代器思想,正在向Iterator接口的传统地位,发起挑战,这个我们后面再详细说明。
package java.util;
import java.util.function.Consumer;
/**
* An iterator over a collection. {@code Iterator} takes the place of
* {@link Enumeration} in the Java Collections Framework. Iterators
* differ from enumerations in two ways:
*
* <ul>
* <li> Iterators allow the caller to remove elements from the
* underlying collection during the iteration with well-defined
* semantics.
* <li> Method names have been improved.
* </ul>
*
* <p>This interface is a member of the
* <a href="{@docRoot}/java.base/java/util/package-summary.html#CollectionsFramework">
* Java Collections Framework</a>.
*
* @apiNote
* An {@link Enumeration} can be converted into an {@code Iterator} by
* using the {@link Enumeration#asIterator} method.
*
* @param <E> the type of elements returned by this iterator
*
* @author Josh Bloch
* @see Collection
* @see ListIterator
* @see Iterable
* @since.2
*/
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}.
* <p>
* 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, unless an overriding class has specified a
* concurrent modification policy.
* <p>
* The behavior of an iterator is unspecified if this method is called
* after a call to the {@link #forEachRemaining forEachRemaining} 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 Illegal State Exception 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.
* <p>
* The behavior of an iterator is unspecified if the action modifies the
* collection in any way (even by calling the {@link #remove remove} method
* or other mutator methods of {@code Iterator} subtypes),
* unless an overriding class has specified a concurrent modification policy.
* <p>
* Subsequent behavior of an iterator is unspecified if the action throws an
* exception.
*
* @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.8
*/
default void forEachRemaining(Consumer<? super E> action) {
Objects.requireNonNull(action);
while (hasNext())
action.accept(next());
}
}
Iterable接口
JDK的Collection框架中的集合,原本是没有Iterable接口的,直到1.5版本,才推出这个接口。我认为这个接口,是集合框架的架构师们,对迭代器设计模式的一个突破,迭代器设计模式里面,原本是没有Iterable接口这个角色的,只有Iterator接口,但是Iterator接口有一个比较独特的地方,它是集合类通过工厂方法来实现的,而不是通过执行Iterator接口来实现,而Iterable接口可谓是神来之笔。让提供迭代器的集合类,有了统一的接口。
可以说,java集合框架的Iterator接口与GOF原著中的迭代器设计模式的思想,是一脉相承的。而Iterable接口在此基础上,进行了创新。有继承,才能有所创新。不继承,空谈创新,无本之木也。
另外,Iterable接口中的forEach()方法,这个方法已经直接向Iterator接口发起了挑战,使用了这个方法之后,你就再也看不到Iterator接口的影子了。
spliterator()方法,也是起源于1.8版本,代表着在多核时代,并行遍历的思想,这个我们后面有机会,再细聊。
/**
* Implementing this interface allows an object to be the target of the enhanced
* {@code for} statement (sometimes called the "for-each loop" statement).
*
* @param <T> the type of elements returned by the iterator
*
* @since.5
* @jls.14.2 The enhanced {@code for} statement
*/
public interface Iterable<T> {
/**
* Returns an iterator over elements of type {@code T}.
*
* @return an Iterator.
*/
Iterator<T> iterator();
/**
* Performs the given action for each element of the {@code Iterable}
* 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.
* <p>
* The behavior of this method is unspecified if the action performs
* side-effects that modify the underlying source of elements, unless an
* overriding class has specified a concurrent modification policy.
*
* @implSpec
* <p>The default implementation behaves as if:
* <pre>{@code
* for (T t : this)
* action.accept(t);
* }</pre>
*
* @param action The action to be performed for each element
* @throws NullPointerException if the specified action is null
* @since.8
*/
default void forEach(Consumer<? super T> action) {
Objects.requireNonNull(action);
for (T t : this) {
action.accept(t);
}
}
/**
* Creates a {@link Spliterator} over the elements described by this
* {@code Iterable}.
*
* @implSpec
* The default implementation creates an
* <em><a href="../util/Spliterator.html#binding">early-binding</a></em>
* spliterator from the iterable's {@code Iterator}. The spliterator
* inherits the <em>fail-fast</em> properties of the iterable's iterator.
*
* @implNote
* The default implementation should usually be overridden. The
* spliterator returned by the default implementation has poor splitting
* capabilities, is unsized, and does not report any spliterator
* characteristics. Implementing classes can nearly always provide a
* better implementation.
*
* @return a {@code Spliterator} over the elements described by this
* {@code Iterable}.
* @since.8
*/
default Spliterator<T> spliterator() {
return Spliterators.spliteratorUnknownSize(iterator(),);
}
}
下面我们深入集合框架的底层,看看他们是如何基于迭代器设计模式,实现iterator接口的。
但是,我们还是需要先看看迭代器设计模式的定义,掌握一定的理论基础。
迭代器设计模式定义解析
Provide a way to access the elements of an aggregate object sequentially without exposing its underlying representation.
—— Gof《Design Patterns: Elements of Reusable Object-Oriented Software》
提供一种方法顺序访问一个集合对象中各个元素,而又不需暴露该对象的内部表示。
——Gof《设计模式:可复用面向对象软件的基础》
这个定义,关键词是 顺序访问 和 内部表示 。
顺序访问
因为对集合的迭代,大家从入行程序员开始,就一直在用,所以,对于什么是顺序访问,应该有自己的体会和理解,这种理解通常是感性的,就像虽然我们不知道什么是“人”的精确定义,却都知道什么是“人”一样。
这里我要从“顺序访问”的对立面来加深大家对顺序访问的认识。顺序访问的对立面是什么呢,我的理解,应该是“随机访问”,也就是访问没有顺序。而我们迭代一个集合时,很明显是有顺序的。如果你是通过迭代,完成“随机访问”的,说明你的方向可能错了。
内部表示
“内部表示”(underlying representation),应该如何理解呢。众所周知,集合表示的是一组对象,而表示一组对象,在开发语言中,可以使用很多数据结构。最原始的非数组莫属,最热门的非List莫属,有点弯弯绕的,如Map,Tree等,都可以代表一个集合。一个集合工具类,底层使用什么样的数据结构,这个“数据结构”就是这个定义里面所说的“内部表示”。
概括起来说,迭代器模式,就是提供一种方法,一种顺序访问一个集合中各个元素的方法,它的宗旨是,访问集合的客户方,不需要知道这个集合,底层到底是什么数据结构。
Why?
问题来了,为什么要这么干,大家要注意,在很多设计模式的讲解中,常常忽略客户方角色,而客户方角色,常常是很多设计模式的幕后大boss,一切改进都是为了服务这个大boss。
为什么客户方不需要知道集合的数据结构,我们可以分析一下,如果客户方知道了这个信息,他是用还是不用,如果不用,知道了有什么意义,如果用,那么客户方是不是就是需要不断的根据集合底层的数据结构,来调整自己的访问方式,进而实现顺序访问呢。这个时候,迭代器模式,就应运而生了。
迭代器设计模式的概念了解了,下面我们就看看常见集合的迭代器底层代码。
List集合的Iterator代码解析
ArrayList类
类图:
List集合,虽然实现类很多,但是大家最熟悉的,使用率最高的,当属ArrayList类了。
我们就看看ArrayList类的源代码。
如下代码所示:
ArrayList类中的iterator()方法,代码很简单,就是创建了一个 Itr类 的对象 。 这个类我们看起来很陌生,陌生是自然的,我们常常作为迭代器的客户方,只知道Iterator接口,而不需要知道Iterator背后到底是什么。
Itr类(这个名字起的有些随意,看来作者的命名规范要提高啊),是ArrayList类的一个私有化的内部类,执行了Iterator接口。
我们可以从Itr类的代码,可以学到下面几个对我们有意义的知识点。
1,这个类有一个int 的 cursor属性 ,它等同于数组里面的索引,它表示的是即将返回的下个元素的索引。
大家知道作者为什么给这个属性起名cursor吗,这是有渊源的,这一点也说明了源代码的作者,对迭代器模式的娴熟程度,迭代器设计模式(Iterator design pattern)还有另外一个名字,就是cursor design pattern,从这个名字,也可以透漏出迭代器设计模式的一个重点,就是对迭代集合时,索引,或者说光标cursor的维护,非常重要。
2,next()方法,在遍历List时,访问的是List的底层数据结构elementData数组。
Object[] elementData = ArrayList.this.elementData;
next方法,不仅仅是一个读操作,它还在读取之后,修改cursor属性的值,将光标往下移动,所以,当集合迭代时,同一个对象是不可能被访问两次的。
3,当遍历List集合时,如果集合被其他线程修改,会抛出ConcurrentModificationException异常。
而如果想在遍历时删除元素,可以使用Iterator里面的remove()方法,它是不会抛出ConcurrentModificationException异常的。
这个类的expectedModCount属性,就是用来判断,集合是否被非法修改的一个重要指标,这其实就是一种乐观锁。
4,另外,ArrayList类里面的iterator()方法和Itr类,其实在它的父类AbstractList里面已经实现了,只不过ArrayList的Itr类,对父类的Itr类,进行了优化了改进。
感兴趣的朋友,可以看看ArrayList的父类AbstractList里面,是如何实现iterator()方法和Itr类的。
/* <p>This class is a member of the
* <a href="{@docRoot}/java.base/java/util/package-summary.html#CollectionsFramework">
* Java Collections Framework</a>.
*
* @param <E> the type of elements in this list
*
* @author Josh Bloch
* @author Neal Gafter
* @see Collection
* @see List
* @see LinkedList
* @see Vector
* @since.2
*/
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
/**
* Returns an iterator over the elements in this list in proper sequence.
*
* <p>The returned iterator is <a href="#fail-fast"><i>fail-fast</i></a>.
*
* @return an iterator over the elements in this list in proper sequence
*/
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 = -; // index of last element returned; -1 if no such
int expectedModCount = modCount;
// prevent creating a synthetic constructor
Itr() {}
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 +;
return (E) elementData[lastRet = i];
}
public void remove() {
if (lastRet <)
throw new IllegalStateException();
checkForComodification();
try {
ArrayList.this.remove(lastRet);
cursor = lastRet;
lastRet = -;
expectedModCount = modCount;
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
@Override
public void forEachRemaining(Consumer<? super E> action) {
Objects.requireNonNull(action);
final int size = ArrayList.this.size;
int i = cursor;
if (i < size) {
final Object[] es = elementData;
if (i >= es.length)
throw new ConcurrentModificationException();
for (; i < size && modCount == expectedModCount; i++)
action.accept(elementAt(es, i));
// update once at end to reduce heap write traffic
cursor = i;
lastRet = i -;
checkForComodification();
}
}
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}
}
Set,Map集合Iterator代码解析
如下代码所示:
JDK中Set集合,我们常用的HashSet底层是HashMap,它的遍历,调用的是HashMap里面的Iterator。
所以,我们研究一下HashMap里面的Iterator即可。
/*
* <p>This class is a member of the
* <a href="{@docRoot}/java.base/java/util/package-summary.html#CollectionsFramework">
* Java Collections Framework</a>.
*
* @param <E> the type of elements maintained by this set
*
* @author Josh Bloch
* @author Neal Gafter
* @see Collection
* @see Set
* @see TreeSet
* @see HashMap
* @since.2
*/
public class HashSet<E>
extends AbstractSet<E>
implements Set<E>, Cloneable, java.io.Serializable
{
@java.io.Serial
static final long serialVersionUID = -L;
private transient HashMap<E,Object> map;
/**
* Constructs a new, empty set; the backing {@code HashMap} instance has
* default initial capacity () and load factor (0.75).
*/
public HashSet() {
map = new HashMap<>();
}
/**
* Returns an iterator over the elements in this set. The elements
* are returned in no particular order.
*
* @return an Iterator over the elements in this set
* @see ConcurrentModificationException
*/
public Iterator<E> iterator() {
return map.keySet().iterator();
}
HashMap类
对于HashMap的遍历,有很多种方式,可以直接遍历value,也可以遍历Key,然后通过key获取相应的value,也可以遍历封装了key和value的entrySet.
我们就从最后一种方式,遍历entrySet,来切入。
HashMap的底层,在java 1.8之前是数组+链表,之后是数组+链表+红黑树。回想上面的List集合,它的底层也是数组。
当不到吧,支撑起Java集合框架的底层数据结构,竟然是数组。而大部分的java程序员,对数组是陌生的,这也是集合框架的功劳,没有让我们直接面对数组。
如下代码中
1,table数组就是HashMap的底层数据结构。它里面存放的Node对象,里面就封装了Map值的Key,Value,当然还有key对应的hash码,还有一个很重要的属性,就是next属性,这个next属性,就会组成链表。当然,数据结构不是我们这次分享的重点,这个我们后面有机会再分享,大家如果感兴趣,可以评论区留言。
2,它里面的Iterator是通过EntrySet类获取的,而EntrySet依赖的是EntryIterator类,EntryIterator类代码很简单,关键要靠它的父类HashIterator, 抽象类HashIterator 是我们理解HashMap 迭代原理的关键。
依赖关系: Iterator->EntrySet->EntryIterator->HashIterator
HashIterator抽象类
抽象类HashIterator里面的代码比较绕,原因就是我们前面说的,HashMap的底层,除了数组,还有链表,那么在迭代器顺序访问时,数组的遍历和链表的遍历都需要考虑,所以代码有些绕,不过认真看,还是能搞明白的。重点代码我已经标出来来。
/* @param <K> the type of keys maintained by this map
* @param <V> the type of mapped values
*
* @author Doug Lea
* @author Josh Bloch
* @author Arthur van Hoff
* @author Neal Gafter
* @see Object#hashCode()
* @see Collection
* @see Map
* @see TreeMap
* @see Hashtable
* @since.2
*/
public class HashMap<K,V> extends AbstractMap<K,V>
implements Map<K,V>, Cloneable, Serializable {
/**
* The table, initialized on first use, and resized as
* necessary. When allocated, length is always a power of two.
* (We also tolerate length zero in some operations to allow
* bootstrapping mechanics that are currently not needed.)
*/
transient Node<K,V>[] table;
/**
* Basic hash bin node, used for most entries. (See below for
* TreeNode subclass, and in LinkedHashMap for its Entry subclass.)
*/
static class Node<K,V> implements Map.Entry<K,V> {
final int hash;
final K key;
V value;
Node<K,V> next;
Node(int hash, K key, V value, Node<K,V> next) {
this.hash = hash;
this.key = key;
this.value = value;
this.next = next;
}
public final K getKey() { return key; }
public final V getValue() { return value; }
public final String toString() { return key + "=" + value; }
public final int hashCode() {
return Objects.hashCode(key) ^ Objects.hashCode(value);
}
public final V setValue(V newValue) {
V oldValue = value;
value = newValue;
return oldValue;
}
public final boolean equals(Object o) {
if (o == this)
return true;
return o instanceof Map.Entry<?, ?> e
&& Objects.equals(key, e.getKey())
&& Objects.equals(value, e.getValue());
}
}
final class EntrySet extends AbstractSet<Map.Entry<K,V>> {
public final int size() { return size; }
public final void clear() { HashMap.this.clear(); }
public final Iterator<Map.Entry<K,V>> iterator() {
return new EntryIterator();
}
...
}
final class EntryIterator extends HashIterator
implements Iterator<Map.Entry<K,V>> {
public final Map.Entry<K,V> next() { return nextNode(); }
}
/* ------------------------------------------------------------ */
// iterators
abstract class HashIterator {
Node<K,V> next; // next entry to return
Node<K,V> current; // current entry
int expectedModCount; // for fast-fail
int index; // current slot
HashIterator() {
expectedModCount = modCount;
Node<K,V>[] t = table;
current = next = null;
index =;
if (t != null && size >) { // advance to first entry
do {} while (index < t.length && (next = t[index++]) == null);
}
}
public final boolean hasNext() {
return next != null;
}
final Node<K,V> nextNode() {
Node<K,V>[] t;
Node<K,V> e = next;
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
if (e == null)
throw new NoSuchElementException();
if ((next = (current = e).next) == null && (t = table) != null) {
do {} while (index < t.length && (next = t[index++]) == null);
}
return e;
}
public final void remove() {
Node<K,V> p = current;
if (p == null)
throw new IllegalStateException();
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
current = null;
removeNode(p.hash, p.key, null, false, false);
expectedModCount = modCount;
}
}
}
我们常用的迭代器,是从前往后,或者从上到下,一条路走到黑的,是不能走回头路的,next,next,还是next,但是业务场景纷繁复杂,种类多样,能走回头路的迭代器,也有它存在的价值,而JDK集合,也提供了这种迭代器,我们可以称之为双向迭代器。
JDK中的双向迭代器
ListIterator接口
它里面不仅有我们熟悉的next()方法,还有previous()方法,向前还是向后,客户说了算。
ArrayList类
ArrayList的ListIterator(),底层是由ListItr来实现的,而这个类,继承了我们刚刚了解的Itr 类,所以代码比较简单,向前找意味着索引要减一。它里面的previous,大家可以重点关注一下。
/*
* @param <E> the type of elements in this list
*
* @author Josh Bloch
* @author Neal Gafter
* @see Collection
* @see List
* @see LinkedList
* @see Vector
* @since.2
*/
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
/**
* Returns a list iterator over the elements in this list (in proper
* sequence).
*
* <p>The returned list iterator is <a href="#fail-fast"><i>fail-fast</i></a>.
*
* @see #listIterator(int)
*/
public ListIterator<E> listIterator() {
return new ListItr();
}
/**
* An optimized version of AbstractList.ListItr
*/
private class ListItr extends Itr implements ListIterator<E> {
ListItr(int index) {
super();
cursor = index;
}
public boolean hasPrevious() {
return cursor !=;
}
public int nextIndex() {
return cursor;
}
public int previousIndex() {
return cursor -;
}
@SuppressWarnings("unchecked")
public E previous() {
checkForComodification();
int i = cursor -;
if (i <)
throw new NoSuchElementException();
Object[] elementData = ArrayList.this.elementData;
if (i >= elementData.length)
throw new ConcurrentModificationException();
cursor = i;
return (E) elementData[lastRet = i];
}
public void set(E e) {
if (lastRet <)
throw new IllegalStateException();
checkForComodification();
try {
ArrayList.this.set(lastRet, e);
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
public void add(E e) {
checkForComodification();
try {
int i = cursor;
ArrayList.this.add(i, e);
cursor = i +;
lastRet = -;
expectedModCount = modCount;
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
}
}
虽然,JDK集合框架中的迭代器已经很方便了,但是码农追求极致的精神,仍然驱使着我们不断的精益求精,不断的创新,这一点,在Google公司的Guava类库,得到了很好的体验。
Guava类库超炫的迭代器工具类
如果说互联网时代的程序员,一大半时间都是在和集合打交道,这个应该不过分,所以集合操作是一个非常高频的操作,这也是为什么集合是面试频率最高的知识点之一。如何提高常用集合操作的效率和便利程度,Guave类库在这方面,一直走在JDK集合类库的前列。
Guave官方文档
Guava的Collections模块,提供了大量集合操作的工具类,而和迭代器相关的是,就属Iterables工具类了,这个工具类从Guava类库2.0版本就存在了,可以说是元老级的工具类。
Iterables工具类
重点方法
如上接口所示,Iterables工具类有很多方法。
Guava类库的架构师,总结出了Iterables工具类最常用的一些方法,如下图所示。
案例
我在这只展示前面几个方法的使用案例,大家感兴趣,可以参考文档学习一下。
package com.geekarchitect.patterns.iterator.guava;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import org.slfj.Logger;
import org.slfj.LoggerFactory;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
/**
* @author 极客架构师@吴念
* @createTime/11/8
*/
public class IterablesDemo {
private static final Logger LOG = LoggerFactory.getLogger(IterablesDemo.class);
/**
* 合并集合并迭代
*/
public void concat(){
List<Integer> list = Lists.newArrayList(1, 2, 3);
List<Integer> list = Lists.newArrayList(4, 5, 6);
List<Integer> list = Lists.newArrayList(7, 8);
Iterable<Integer> iterable = Iterables.concat(list, list2, list3);
Iterator<Integer> iterator= iterable.iterator();
while (iterator.hasNext()){
LOG.info(iterator.next().toString());
}
}
/**
* 分区迭代,可以模拟类似分页或者其他批量操作的业务场景
*/
public void partition(){
List<Integer> list = Lists.newArrayList(1, 2,3,4,5,6,7,8,9,10,11);
Iterable<Integer> iterable = Iterables.concat(list1);
Iterable<List<Integer>> iterable= Iterables.partition(iterable1,5);
Iterator<List<Integer>> iterator=iterable2.iterator();
while (iterator.hasNext()){
LOG.info(iterator.next().toString());
}
}
}
运行结果
FluentIterable 流迭代器
Guava类库中的迭代器,也在随着时代而发展,而FluentIterable工具类,就是流时代的产物,它是从Guava类库的12.0版本才开始的,但是仍然领先于java8的流模块。
从这个类注释的第一句话,我们可以看出这个工具类,也即将退出历史舞台,而代替它的,就是java8的流模块。
A discouraged (but not deprecated) precursor to Java’s superior Stream library.
一个不鼓励使用(但没有弃用)的,Java高级流类库的前身。
案例
下面的代码,是FluentIterable工具类的作者提供的,同样功能的代码,上半部分是用FluentIterable工具实现的,下半部分,是用java的Stream模块实现的,两者何其相似。好的工具类,难免有这样的命运,被JDK直接收编或者模仿。这是一种不幸也是一种荣耀。
JDK第三代迭代器-流(Stream)时代的迭代器
Java8是java发展史上的,一个具有划时代意义的版本。虽然java8之后,还有很多版本,最新的已经到java19了,但是有影响力的不多。稍微有点影响力的是java11版本,不过生产环境中,用的还不是很多。
Java8引入了很多重要的编程概念,比如流编程,函数式编程,Lambda表达式等等。对迭代器的发展也产生了很大应用,在新的编程思想下,我们几乎见不到Iterator接口的影子了。Iterator接口被淹没在历史的洪流中。
清代著名文学家,史学家赵翼的诗有犹言在耳。
《论诗·其二》
李杜诗篇万口传,至今已觉不新鲜。
江山代有才人出,各领风骚数百年。
Iterator接口
当然,为了支持新的编程模式,java集合框架的Iterator接口,也增加了forEachRemaining()方法。这个方法的参数 Consumer接口,(它 就是一个函数接口)。
package java.util;
import java.util.function.Consumer;
/**
* An iterator over a collection. {@code Iterator} takes the place of
* {@link Enumeration} in the Java Collections Framework. Iterators
* differ from enumerations in two ways:
*
* <ul>
* <li> Iterators allow the caller to remove elements from the
* underlying collection during the iteration with well-defined
* semantics.
* <li> Method names have been improved.
* </ul>
*
* <p>This interface is a member of the
* <a href="{@docRoot}/java.base/java/util/package-summary.html#CollectionsFramework">
* Java Collections Framework</a>.
*
* @apiNote
* An {@link Enumeration} can be converted into an {@code Iterator} by
* using the {@link Enumeration#asIterator} method.
*
* @param <E> the type of elements returned by this iterator
*
* @author Josh Bloch
* @see Collection
* @see ListIterator
* @see Iterable
* @since.2
*/
public interface Iterator<E> {
...
/**
* 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.
* <p>
* The behavior of an iterator is unspecified if the action modifies the
* collection in any way (even by calling the {@link #remove remove} method
* or other mutator methods of {@code Iterator} subtypes),
* unless an overriding class has specified a concurrent modification policy.
* <p>
* Subsequent behavior of an iterator is unspecified if the action throws an
* exception.
*
* @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.8
*/
default void forEachRemaining(Consumer<? super E> action) {
Objects.requireNonNull(action);
while (hasNext())
action.accept(next());
}
}
使用案例
Iterable接口
Iterable接口,增加了forEach()方法.
/**
* Implementing this interface allows an object to be the target of the enhanced
* {@code for} statement (sometimes called the "for-each loop" statement).
*
* @param <T> the type of elements returned by the iterator
*
* @since.5
* @jls.14.2 The enhanced {@code for} statement
*/
public interface Iterable<T> {
...
/**
* Performs the given action for each element of the {@code Iterable}
* 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.
* <p>
* The behavior of this method is unspecified if the action performs
* side-effects that modify the underlying source of elements, unless an
* overriding class has specified a concurrent modification policy.
*
* @implSpec
* <p>The default implementation behaves as if:
* <pre>{@code
* for (T t : this)
* action.accept(t);
* }</pre>
*
* @param action The action to be performed for each element
* @throws NullPointerException if the specified action is null
* @since.8
*/
default void forEach(Consumer<? super T> action) {
Objects.requireNonNull(action);
for (T t : this) {
action.accept(t);
}
}
/**
* Creates a {@link Spliterator} over the elements described by this
* {@code Iterable}.
*
* @implSpec
* The default implementation creates an
* <em><a href="../util/Spliterator.html#binding">early-binding</a></em>
* spliterator from the iterable's {@code Iterator}. The spliterator
* inherits the <em>fail-fast</em> properties of the iterable's iterator.
*
* @implNote
* The default implementation should usually be overridden. The
* spliterator returned by the default implementation has poor splitting
* capabilities, is unsized, and does not report any spliterator
* characteristics. Implementing classes can nearly always provide a
* better implementation.
*
* @return a {@code Spliterator} over the elements described by this
* {@code Iterable}.
* @since.8
*/
default Spliterator<T> spliterator() {
return Spliterators.spliteratorUnknownSize(iterator(),);
}
}
使用案例
Stream接口
上面这些都是小Case,真正改变我们集合操作的,是java8中引入的Stream接口,而它的前身,就是Guava类库里面的FluentIterable工具类。它不仅仅是为了简化我们的集合操作,底层也有质的变化,特别是在CPU多核时代下,提供的并行执行,大大提高了集合操作,在多核CPU系统上的代码执行效率。
我后面计划分享一个系列《Java流编程》和《Java函数式编程》,不知大家是否感兴趣,喜欢的话可以在评论区留言。
Stream类,里面的方法很多,筛选,切片,映射,查找,规约等等,而直接和迭代器相关的,就是它里面的forEach()方法了。
使用案例
要搞清楚Stream接口的forEach方法的底层原理,了解它与传统的Iterator接口有什么本质区别,就必须对流,函数式编程以及Lambda表达式等等有一定的了解,这个我们今天就不展开了。
下期预告
迭代器模式,我们就分享到这里,虽然迭代器模式,在常见的开源项目中,很难找到自定义的迭代器案例,但是,确实还是有一些项目,自定义了自己的迭代器,这个我会在后面的《源码说》里面给大家分享,下期,我将分享行为型设计模式中的观察者设计模式(Observer Pattern),敬请期待。
极客架构师,专注架构师成长。