【问题标题】:Iterator vs for迭代器与 for
【发布时间】:2014-04-11 15:39:55
【问题描述】:

我在一次采访中被问到使用迭代器比for 循环有什么优势,或者使用for 循环比迭代器有什么优势?

任何人都可以回答这个问题吗?

【问题讨论】:

  • for-each 循环在底层使用了一个迭代器。区别只是可读性(因此是可维护性)。
  • 好吧;你为什么说Iterator
  • 没有什么能强迫你立即回答。您可以推理不同类型的集合、每个代码在幕后的作用,并展示您的推理能力,即使您无法制定明确的答案。这就是重要的:能够思考。我会这样开始我的回答:“嗯,我不知道。我想这取决于你想做什么,以及收集的类型是什么。必须有充分的理由同时使用两者,但它必须取决于这种情况。让我们想象一下 ArrayList 会发生什么。然后是 LinkedList 等等等等。”
  • 我完全同意@JBNizet。说我不知道​​它比选择一个随机选项好多少——这给我的印象是你会在编程时这样做;不假思索地选择想到的第一件事。您可以尝试解释您在该领域的知识,而不是说您的知识不足以给出明智的答案。
  • 当前的两个答案都不完整。首先,有 2 种 for 循环,它们的行为非常不同。一种使用索引(这并不总是可能的),另一种在幕后使用迭代器。所以你实际上有 3 个循环来比较。然后你可以用不同的术语来比较它们:性能、可读性、易错性、能力。 Iterator 可以做一些 foreach 循环不能做的事情。但是迭代器更危险,可读性更差。并且使用索引访问元素仅适用于列表,但如果它是链表则效率不高。所以没有“最好”的方式。

标签: java for-loop iterator


【解决方案1】:

首先,有 2 种 for 循环,它们的行为非常不同。一个使用索引:

for (int i = 0; i < list.size(); i++) {
    Thing t = list.get(i);
    ...
}

这种循环并不总是可能的。例如,List 有索引,但 Set 没有,因为它们是无序的集合。

另一个,foreach 循环在幕后使用了一个迭代器:

for (Thing thing : list) {
    ...
}

这适用于各种 Iterable 集合(或数组)

最后,您可以使用 Iterator,它也适用于任何 Iterable:

for (Iterator<Thing> it = list.iterator(); it.hasNext(); ) {
    Thing t = it.next();
    ...
} 

所以你实际上有 3 个循环要比较。

您可以用不同的术语来比较它们:性能、可读性、易错性、能力。

迭代器可以做一些 foreach 循环不能做的事情。例如,如果迭代器支持,您可以在迭代时删除元素:

for (Iterator<Thing> it = list.iterator(); it.hasNext(); ) {
    Thing t = it.next();
    if (shouldBeDeleted(thing) {
        it.remove();
    }
} 

列表还提供了可以双向迭代的迭代器。 foreach 循环只从头到尾迭代。

但迭代器更危险且可读性更差。当你只需要一个 foreach 循环时,它是最易读的解决方案。使用迭代器,您可以执行以下操作,这将是一个错误:

for (Iterator<Thing> it = list.iterator(); it.hasNext(); ) {
    System.out.println(it.next().getFoo());
    System.out.println(it.next().getBar());
} 

foreach 循环不允许发生此类错误。

对于由数组支持的集合,使用索引访问元素的效率稍高一些。但是,如果您改变主意并使用 LinkedList 而不是 ArrayList,那么性能会突然变得很糟糕,因为每次访问 list.get(i) 时,链表都必须循环遍历其所有元素,直到第 i 个元素。迭代器(以及因此的 foreach 循环)没有这个问题。它总是使用最好的方法来遍历给定集合的元素,因为集合本身有它自己的 Iterator 实现。

我的一般经验法则是:使用 foreach 循环,除非您真的需要迭代器的功能。当我需要访问循环内的索引时,我只会使用带有数组索引的 for 循环。

【讨论】:

  • 当您使用迭代器删除集合中的一个或多个元素时,这样做是安全的。当您使用 for-each 或 for 循环时,删除要么是不允许的,要么会导致错误,因为当在集合上循环时删除时,集合中每个元素的索引都会更改。因此,您应该使用反向 for 循环,或使用迭代器来防止引入错误。另请参阅answer
  • 你说的“我”在英语中是一个挑战。
  • 也许他可以用“i-th”代替它?
【解决方案2】:

迭代器优势:

  • 能够从集合中移除元素。
  • 能够使用next()previous() 向前和向后移动。
  • 能够使用hasNext()检查是否有更多元素。

循环仅设计用于迭代Collection,因此如果您只想迭代Collection,最好使用循环,例如for-Each,但如果您想要更多,您可以使用迭代器.

【讨论】:

  • 并且使用ListIterator,您还可以add 并在任意点开始迭代。
  • @Salah:为什么迭代器可以在迭代集合时删除元素?
【解决方案3】:

Iterator 和经典 for 循环之间的主要区别,除了可以访问或不可以访问您正在迭代的项目的索引的明显区别之外,是使用 Iterator 从底层集合实现中抽象出客户端代码,请允许我详细说明。

当您的代码使用迭代器时,无论是这种形式

for(Item element : myCollection) { ... }

这个表格

Iterator<Item> iterator = myCollection.iterator();
while(iterator.hasNext()) {    
    Item element = iterator.next();    
    ... 
}

或者这个表格

for(Iterator iterator = myCollection.iterator(); iterator.hasNext(); ) {
   Item element = iterator.next();
   ...
}

您的代码所说的是“我不关心集合的类型及其实现,我只关心我可以遍历它的元素”。这通常是更好的方法,因为它使您的代码更加解耦。

另一方面,如果您使用的是经典的 for 循环,如

for(int i = 0; i < myCollection.size(); i++) {
   Item element = myCollection.get(i);
   ...
}

您的代码是说,我需要知道集合的类型,因为我需要以特定方式遍历它的元素,我还可能要检查空值或根据迭代顺序计算一些结果.这会使您的代码更加脆弱,因为如果您收到的集合类型在任何时候发生变化,都会影响您的代码的工作方式。

总结起来,区别不在于速度或内存使用量,而在于解耦代码,以便更灵活地应对变化。

【讨论】:

    【解决方案4】:

    如果您按数字(例如“i”)访问数据,则使用数组时速度很快。因为它直接进入元素

    但是,其他数据结构(例如树、列表)需要更多时间,因为它从第一个元素开始到目标元素。当你使用列表时。它需要时间 O(n)。所以,要慢一点。

    如果你使用迭代器,编译器就会知道你在哪里。所以它需要 O(1) (因为它从当前位置开始)

    最后,如果您只使用支持直接访问的数组或数据结构(例如 java 中的 arraylist)。 “a[i]”很好。但是,当你使用其他数据结构时,迭代器效率更高

    【讨论】:

    • 所以我应该说这取决于要求。
    • +1 指出在 Java Collections 上使用按索引循环的缺点。值得指出的是,增强的 for 循环在底层使用了 Iterator
    • 您的其他数据结构示例没有帮助 - ArrayList 提供索引访问。也许更新以包含特定的 Java 集合? (例如 ArrayList 与 LinkedList)
    【解决方案5】:

    与其他答案不同,我想指出另一件事;

    如果您需要在代码中的多个位置执行迭代,您最终可能会重复逻辑。这显然不是一个非常可扩展的方法。相反,我们需要一种方法,将选择数据的逻辑与实际处理数据的代码分开。

    迭代器通过提供用于循环一组数据的通用接口来解决这些问题,从而隐藏底层数据结构或存储机制(例如数组)。

    • 迭代器是一个概念,而不是实现。
    • 迭代器提供了许多用于遍历和访问数据的操作。
    • 迭代器可以包装任何数据结构,如数组。
    • 使用迭代器的一个更有趣和有用的优点是能够包装或装饰另一个迭代器以过滤返回值
    • 迭代器可能是线程安全的,而单独的 for 循环则不能,因为它直接访问元素。唯一流行的线程安全迭代器是 CopyOnWriteArrayList,但它众所周知且经常使用,因此值得一提。

    这是书上的https://www.amazon.com/Beginning-Algorithms-Simon-Harris/dp/0764596748

    【讨论】:

      【解决方案6】:

      我偶然发现了这个问题。答案在于Iterator试图解决的问题:

      • 访问和遍历聚合对象的元素而不暴露其表示
      • 为聚合对象定义遍历操作而不更改其接口

      【讨论】:

        猜你喜欢
        • 2015-05-21
        • 1970-01-01
        • 2016-10-22
        • 2010-10-27
        • 2018-05-24
        • 2014-09-30
        • 1970-01-01
        • 1970-01-01
        • 2019-06-04
        相关资源
        最近更新 更多