likes
comments
collection
share

大聪明教你学Java | Iterator 和 Iterable 的那些事

作者站长头像
站长
· 阅读数 16

前言

在面试的时候,如果面试官问你:Iterator 和 Iterable 有什么区别,你会怎么回答这个问题呢?要是一年前的我来回答这个问题的话,估计我直接就如鲠在喉、哑口无言了,不过现在的我还是能跟面试官聊上几句的,那么今天就和各位小伙伴分享一下我对 Iterator 和 Iterable 的理解😊。

Iterator 和 Iterable

🍓🍓定义🍓🍓

在聊 Iterator 和 Iterable 之前,我们得先明白二者的定义,俗话说“繁琐问题必有猥琐解法”,我们用一个“猥琐”的方式来理解二者的含义👇

Iterable : 英语好的小伙伴应该都知道,以 able 结尾的单词,表示的含义都是【可以...】或【支持...】,那么 Iterable 所代表的含义也就是可迭代的、支持迭代的,因此我们可以知道,实现了 Iterable 接口的对象是支持迭代,是可迭代的。

Iterator : 在英语中以 or 结尾的单词表示的含义是【 ...样的人】或【 ...者】,就像 creator 表示的是创作者的意思。那么这里也是一样的,iterator 就是迭代者,我们一般叫迭代器,它就是提供迭代机制的对象,具体如何迭代,都是 Iterator 接口来规范的。 P.S. 在 Java 设计模式中也有一种模式叫迭代器模式,Iterator 就是迭代器模式的一个应用例子

🍓🍓Iterable源码及方法🍓🍓

首先贴上 Iterable 接口的源码👇

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.  Unless otherwise specified by the implementing class,
     * actions are performed in the order of iteration (if an iteration order
     * is specified).  Exceptions thrown by the action are relayed to the
     * caller.
     *
     * @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 1.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="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 1.8
     */
    default Spliterator<T> spliterator() {
        return Spliterators.spliteratorUnknownSize(iterator(), 0);
    }
}

通过源码我们可以到 Iterable 接口中有三个方法,分别是:

  • Iterator iterator() : 该接口主要是用来返回T类型的元素上的一个迭代器。
  • default void forEach(Consumer<? super T> action) : 该方法是循环输出,对内部元素进行遍历并对元素进行指定的操作。
  • default Spliterator spliterator(): 该方法提供了一个可以并行遍历元素的迭代器,以适应现在cpu多核时代并行遍历的需求。

🍓🍓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 中包含了四个方法,分别是:

  • boolean hasNext(): 判断需要遍历的集合是否存在下一个元素,即如果被迭代遍历的集合还没有被遍历完,返回 true
  • E next(): 返回集合里面的下一个元素
  • default void remove(): 删除集合里面上一次 next() 方法返回的元素
  • default void forEachRemaining(Consumer<? super E> action): 该方法是JDK 1.8后新增默认方法,含义是使用Lambda表达式来遍历集合元素

看到这可能有些小伙伴就会问了: default void forEachRemaining(Consumer<? super E> action) 方法怎么用呢?它和 forEach() 方法又有什么区别呢?我们接着往下看~

forEachRemaining 方法是使用就很简单了,直接给大家贴一个示例代码👇

public static void main(String[] args) {
    List list = new ArrayList<String>();
    list.add("我在");
    list.add("人民广场");
    list.add("吃着炸鸡");
    list.iterator().forEachRemaining(str-> System.out.println(str));
}

关于二者的区别也很简单,我们通过源码就可以一目了然~

🥝🥝forEachRemaining()源码🥝🥝

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

🥝🥝forEach()源码🥝🥝

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

这两个方法说是跟孙悟空和六耳猕猴一样,二者长得太像了,他们都可以遍历集合而且都是接口的默认方法,唯一不同的地方就是循环方式不一样。前者内部是通过使用迭代器来遍历的所有元素;而后者内部使用的是增强 for 循环遍历的元素,不同的遍历方式也导致了 forEach() 方法可以多次调用,而 forEachRemaining() 方法在第二次调用时不会做任何操作,因为不会有下一个元素了。

🍓🍓Iterator 和 Iterable 的区别🍓🍓

通过上面的讲解,相信各位小伙伴对 Iterator 和 Iterable 有了一些自己的见解,最后我们再一起总结一下二者之间的区别。

Iterator 是迭代器类,而 Iterable 是一个接口,约束某类是否可迭代,某个类只要实现了 Iterable 接口就可以使用 foreach 进行迭代。同时 Iterable 中又封装了 Iterator 接口,只要实现了 Iterable 接口的类,也就可以使用 Iterator 迭代器了。集合Collection、List、Set都是 Iterable 的实现类,所以他们及其他们的子类都可以使用 foreach 进行迭代。

上面我们提到了 Collection、List、Set 都是 Iterable 的实现类,那为什么一定要去实现 Iterable 这个接口呢?直接实现 Iterator 接口不行吗?答案肯定是不行的,原因也很简单👇

Iterator 接口中的核心方法 next() 或 hasNext() 是依赖于迭代器的当前迭代位置的。 如果Collection直接实现Iterator接口,则导致集合对象中包含当前迭代位置的数据(可以理解成指针)。 当集合在不同方法间被传递时,由于当前迭代位置不可预知,那么 next() 方法的结果也就成了一个未知数;而 Iterable 则不然,每次调用都会返回一个从头开始计数的迭代器,并且多个迭代器是互不干扰的,所以 Collection、List、Set 集合实现的是 Iterable 而非 Iterator 。

小结

本人经验有限,有些地方可能讲的没有特别到位,如果您在阅读的时候想到了什么问题,欢迎在评论区留言,我们后续再一一探讨🙇‍

希望各位小伙伴动动自己可爱的小手,来一波点赞+关注 (✿◡‿◡) 让更多小伙伴看到这篇文章~ 蟹蟹呦(●'◡'●)

如果文章中有错误,欢迎大家留言指正;若您有更好、更独到的理解,欢迎您在留言区留下您的宝贵想法。

爱你所爱 行你所行 听从你心 无问东西