Iterator原理及实现

Iteratable

在Java中,容器可进行迭代操作,使用Iteratable接口来标识:

Iteratable中的接口如下:

public interface Iterable<T> {

    //返回迭代器
    Iterator<T> iterator();

    //对每个元素执行action操作,提供了默认实现
    //@since 1.8
    default void forEach(Consumer<? super T> action) {
        Objects.requireNonNull(action);
        for (T t : this) {
            action.accept(t);
        }
    }

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

Collection接口继承了Iterable接口,这表明所有的Colection均支持迭代操作:

foreach示例代码

Iterator

Iterator,迭代器,用来遍历Collection中的元素。对于Collection的所有实现类而言,必须实现以下接口,即必须返回一个迭代器:

Iterator中的接口如下:

主要原理

  • 创建指向某个CollectionIterator对象时,Iterator内部的游标指向的第一个元素。

  • 调用hasNext()的时候,只是判断下一个元素的有无,并不移动游标。

  • 调用next()的时候,返回游标指向的元素,之后向下移动游标,使其指向Collection中的下一个元素。

  • 调用remove(),删除当前游标上次指向的元素

ArrayList中的实现

ArrayList中有两个Iterator的实现,一个Itr,另一个是增强的ListItr(提供了向前移动/遍历的功能,是ListIterator的实现),这里以Itr为例。

可知,在ArrayList内部的Iterator实现中,内部维护了一个游标cursor,该cursor初始化为0,每调用一次next()方法时,在返回cursor指向的元素之后,将curosr值加1;同时,使用变量lastRet来记录上次next()方法返回的元素的下标。在调用remove()方法后,由于实际数据量少了1个,所以将cursor指向lastRet,以便以便下次next()调用时可以返回正确的结果。

同时,也可以发现:在实现hasNext()/next()/remove()等方法时,均是直接操作或依赖ArrayList底层的数据结构。

在Collection其他子类中的实现中,也是如此。由于Collection中各实现类底层结构不尽相同,遍历时所采用的方式也就不一样,所以每个实现类一般都会各个维护自身的Iterator实现。

Map中的迭代

Map是键值对的集合。对于给定的一个map,我们可以:

  • 使用map.keySet() 方法来获取所有的key的集合,类型为Set<K>(key值不会重复);

  • 使用map.values() 方法来获取所有的value的集合,类型为Collection<V>;

  • 使用map.entrySet()方法来获取所有键值对的集合,类型为Set<Map.Entry<K,V>>;

这几种Map的迭代方式,其实是降级到Collection再进行的。

下面是对Map进行遍历的一些常见用法:

但是,在JDK1.8及之后,HashMap中多了一种遍历的方法,该方法可以更直观地看到迭代方式的实现直接依赖底层数据结构:

而这种方式,使用时写法更为简明(借助lambda表达式):

参考

迭代器iterator原理和设计模式

Last updated