JDK集合框架创始人 Google首席java架构师 设计模式的又一贡献

Java
343
0
0
2023-12-20

一个已经融入 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);
    }

} 

运行结果

JDK集合框架创始人 Google首席java架构师 设计模式的又一贡献

补充说明

demo3中java的增强for语句,下面的反编译之后代码,暴露了它的本质,它的语法比Iterator简单,但是换汤不换药,底层仍然是Iterator,它只是一颗 语法糖 而已。

JDK集合框架创始人 Google首席java架构师 设计模式的又一贡献

源代码

JDK集合框架创始人 Google首席java架构师 设计模式的又一贡献

反编译之后的代码

JDK集合框架创始人 Google首席java架构师 设计模式的又一贡献

JDK第一代迭代器

Enumeration接口

JDK中的迭代器,最早的当属Enumeration接口,它是从java1.0版本开始的,它里面定义了两个方法:hasMoreElements()和nextElement(),配合这个接口的,是老一辈的集合工具类 Vector 和Hashtable。这两个类,目前大家使用的比较少,只有在面试的时候,经常会问Vector和ArrayLIst有什么区别,Hashtable和 HashMap 有什么区别,不知道答案的可以百度一下,这不是我们本次分享的重点。


JDK集合框架创始人 Google首席java架构师 设计模式的又一贡献



《effective Java》 的作者 Josh Bloch 入职Sun公司,从安全组调入核心平台组之后,开始对JDK的集合进行了大刀阔斧的革命,他成为了新一代的JDK集合框架的创始人,也就是我们现在所熟悉的JDK框架集合。Josh Bloch也因此在2004年,获得了SUN公司的杰出工程师(Distinguished Engineer)称号。我的架构师书房栏目,后面将计划分享Josh Bloch的这本荣膺2002年度Jolt大奖的书《Effective Java》,感兴趣的可以评论区留言哦。

JDK集合框架创始人 Google首席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接口在此基础上,进行了创新。有继承,才能有所创新。不继承,空谈创新,无本之木也。

JDK集合框架创始人 Google首席java架构师 设计模式的又一贡献

另外,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());
        }
    }



} 

运行结果

JDK集合框架创始人 Google首席java架构师 设计模式的又一贡献

FluentIterable 流迭代器

Guava类库中的迭代器,也在随着时代而发展,而FluentIterable工具类,就是流时代的产物,它是从Guava类库的12.0版本才开始的,但是仍然领先于java8的流模块。

从这个类注释的第一句话,我们可以看出这个工具类,也即将退出历史舞台,而代替它的,就是java8的流模块。

A discouraged (but not deprecated) precursor to Java’s superior Stream library.

一个不鼓励使用(但没有弃用)的,Java高级流类库的前身。

JDK集合框架创始人 Google首席java架构师 设计模式的又一贡献

JDK集合框架创始人 Google首席java架构师 设计模式的又一贡献

案例

下面的代码,是FluentIterable工具类的作者提供的,同样功能的代码,上半部分是用FluentIterable工具实现的,下半部分,是用java的Stream模块实现的,两者何其相似。好的工具类,难免有这样的命运,被JDK直接收编或者模仿。这是一种不幸也是一种荣耀。

JDK集合框架创始人 Google首席java架构师 设计模式的又一贡献

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),敬请期待。

极客架构师,专注架构师成长。