海康威视一面:Iterator与Iterable有什么区别?

沉默王二

共 406字,需浏览 1分钟

 ·

2021-10-17 13:43

那天,小二去海康威视面试,面试官老王一上来就甩给了他一道面试题:请问 Iterator与Iterable有什么区别?小二差点笑出声,因为一年前,也就是 2021 年,他在《Java 程序员进阶之路》专栏上看到过这题😆。

PS:星标这种事,只能求,不求没效果,come on。《Java 程序员进阶之路》在 GitHub 上已经收获了 426 枚星标,小伙伴们赶紧去点点了,冲 500!

https://github.com/itwanger/toBeBetterJavaer


在 Java 中,我们对 List 进行遍历的时候,主要有这么三种方式。

第一种:for 循环。

for (int i = 0; i < list.size(); i++) {
    System.out.print(list.get(i) + ",");
}

第二种:迭代器。

Iterator it = list.iterator();
while (it.hasNext()) {
    System.out.print(it.next() + ",");
}

第三种:for-each。

for (String str : list) {
    System.out.print(str + ",");
}

第一种我们略过,第二种用的是 Iterator,第三种看起来是 for-each,其实背后也是 Iterator,看一下反编译后的代码就明白了。

Iterator var3 = list.iterator();

while(var3.hasNext()) {
    String str = (String)var3.next();
    System.out.print(str + ",");
}

for-each 只不过是个语法糖,让我们在遍历 List 的时候代码更简洁明了。

Iterator 是个接口,JDK 1.2 的时候就有了,用来改进 Enumeration:

  • 允许删除元素(增加了 remove 方法)
  • 优化了方法名(Enumeration 中是 hasMoreElements 和 nextElement,不简洁)

来看一下 Iterator 的源码:

public interface Iterator<E{
    // 判断集合中是否存在下一个对象
    boolean hasNext();
    // 返回集合中的下一个对象,并将访问指针移动一位
    next();
    // 删除集合中调用next()方法返回的对象
    default void remove() {
        throw new UnsupportedOperationException("remove");
    }
}

JDK 1.8 时,Iterable 接口中新增了 forEach 方法:

default void forEach(Consumersuper T> action) {
    Objects.requireNonNull(action);
    for (T t : this) {
        action.accept(t);
    }
}

它对 Iterable 的每个元素执行给定操作,具体指定的操作需要自己写Consumer接口通过accept方法回调出来。

List list = new ArrayList<>(Arrays.asList(123));
list.forEach(integer -> System.out.println(integer));

写得更浅显易懂点,就是:

List list = new ArrayList<>(Arrays.asList(123));
list.forEach(new Consumer() {
    @Override
    public void accept(Integer integer) {
        System.out.println(integer);
    }
});

如果我们仔细观察ArrayList 或者 LinkedList 的“户口本”就会发现,并没有直接找到 Iterator 的影子。

反而找到了 Iterable!

public interface Iterable<T{
    Iterator iterator();
}

也就是说,List 的关系图谱中并没有直接使用 Iterator,而是使用 Iterable 做了过渡。

回头再来看一下第二种遍历 List 的方式。

Iterator it = list.iterator();
while (it.hasNext()) {
}

发现刚好呼应上了。拿 ArrayList 来说吧,它重写了 Iterable 接口的 iterator 方法:

public Iterator iterator() {
    return new Itr();
}

返回的对象 Itr 是个内部类,实现了 Iterator 接口,并且按照自己的方式重写了 hasNext、next、remove 等方法。

private class Itr implements Iterator<E{

    public boolean hasNext() {
        return cursor != size;
    }

    @SuppressWarnings("unchecked")
    public E next() {
        Object[] elementData = ArrayList.this.elementData;
        cursor = i + 1;
        return (E) elementData[lastRet = i];
    }

    public void remove() {
        try {
            ArrayList.this.remove(lastRet);
            cursor = lastRet;
            lastRet = -1;
            expectedModCount = modCount;
        } catch (IndexOutOfBoundsException ex) {
            throw new ConcurrentModificationException();
        }
    }

}

那可能有些小伙伴会问:为什么不直接将 Iterator 中的核心方法 hasNext、next 放到 Iterable 接口中呢?直接像下面这样使用不是更方便?

Iterable it = list.iterator();
while (it.hasNext()) {
}

从英文单词的后缀语法上来看,(Iterable)able 表示这个 List 是支持迭代的,而 (Iterator)tor 表示这个 List 是如何迭代的。

支持迭代与具体怎么迭代显然不能混在一起,否则就乱的一笔。还是各司其职的好。

想一下,如果把 Iterator 和 Iterable 合并,for-each 这种遍历 List 的方式是不是就不好办了?

原则上,只要一个 List 实现了 Iterable 接口,那么它就可以使用 for-each 这种方式来遍历,那具体该怎么遍历,还是要看它自己是怎么实现 Iterator 接口的。

Map 就没办法直接使用 for-each,因为 Map 没有实现 Iterable 接口,只有通过 map.entrySet()map.keySet()map.values() 这种返回一个  Collection 的方式才能 使用 for-each。

如果我们仔细研究 LinkedList 的源码就会发现,LinkedList 并没有直接重写 Iterable 接口的 iterator 方法,而是由它的父类 AbstractSequentialList 来完成。

public Iterator iterator() {
    return listIterator();
}

LinkedList 重写了 listIterator 方法:

public ListIterator listIterator(int index) {
    checkPositionIndex(index);
    return new ListItr(index);
}

这里我们发现了一个新的迭代器 ListIterator,它继承了 Iterator 接口,在遍历List 时可以从任意下标开始遍历,而且支持双向遍历。

public interface ListIterator<Eextends Iterator<E{
    boolean hasNext();
    next();
    boolean hasPrevious();
    previous();
}

我们知道,集合(Collection)不仅有 List,还有 Map 和 Set,那 Iterator 不仅支持 List,还支持 Set,但 ListIterator 就只支持 List。

那可能有些小伙伴会问:为什么不直接让 List 实现 Iterator 接口,而是要用内部类来实现呢?

这是因为有些 List 可能会有多种遍历方式,比如说 LinkedList,除了支持正序的遍历方式,还支持逆序的遍历方式——DescendingIterator:

private class DescendingIterator implements Iterator<E{
    private final ListItr itr = new ListItr(size());
    public boolean hasNext() {
        return itr.hasPrevious();
    }
    public E next() {
        return itr.previous();
    }
    public void remove() {
        itr.remove();
    }
}

可以看得到,DescendingIterator 刚好利用了 ListIterator 向前遍历的方式。可以通过以下的方式来使用:

Iterator it = list.descendingIterator();
while (it.hasNext()) {
}

好了,关于Iterator与Iterable我们就先聊这么多,总结两点:

  • 学会深入思考,一点点抽丝剥茧,多想想为什么这样实现,很多问题没有自己想象中的那么复杂。
  • 遇到疑惑不放弃,这是提升自己最好的机会,遇到某个疑难的点,解决的过程中会挖掘出很多相关的东西。

这是《Java 程序员进阶之路》专栏的第 62 篇。Java 程序员进阶之路,风趣幽默、通俗易懂,对 Java 初学者极度友好和舒适😘,内容包括但不限于 Java 语法、Java 集合框架、Java IO、Java 并发编程、Java 虚拟机等核心知识点。

点击上方名片,发送消息「03」 就可以获取《Java 程序员进阶之路》的 PDF 版了,一起成为更好的 Java 工程师。

浏览 13
点赞
评论
收藏
分享

手机扫一扫分享

举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

举报