为什么在Java中我们需要使用ArrayList迭代器?

40

我正在阅读针对问题“Do we ever need to use Iterators on ArrayList?”提供的答案。

在答案中,用户说了这样一句话:“使用 ArrayList 的迭代器的一个重要用例是在迭代时删除元素”。

即使在 Java 中使用 ArrayList 的 remove 方法也可以实现此目的。我的问题是为什么我们需要在 ArrayList 中使用迭代器?

考虑以下代码:

import java.util.*;
public class ocajp66 {
    public static void main(String[] args) {
        ArrayList a = new ArrayList();
        for (int i = 0; i < 10; i++) {
            a.add(i);
        }
        System.out.printf("BEFORE ITERATOR\n");
        for (int i = 0; i < a.size(); i++) {
            System.out.printf("I:%d\n", a.get(i));
        }
        System.out.printf("AFTER ITERATOR\n");
        Iterator i = a.iterator();
        while (i.hasNext()) {
            System.out.printf("I:%d\n", i.next());
        }
    }
}

有谁能解释一下迭代器的重要性吗?如果您能用代码来解释,那将是非常棒的。


这甚至可以使用 Java 中的 ArrayList 的 remove 方法来实现。你试过吗? - kosa
1
这个问题与链接的那个问题有何不同? - Howard
作为Java的初学者,我想了解迭代器在Java中的重要性,为什么我们需要它,何时可以使用循环进行修改/删除/插入。 - Karthik Rk
@Howard,在链接的问题中,我无法理解迭代器的意义。 - Karthik Rk
5个回答

60

正如您所述,当您想在迭代数组内容时删除数据时,需要使用迭代器。如果您不使用迭代器而仅仅是使用for循环并在其中使用remove方法,则会出现异常,因为在迭代过程中数组内容发生了变化。例如:您可能认为在for循环开始时数组大小为10,但一旦删除元素,情况就不再是这样了...因此当您到达最后一个循环时,可能会出现IndexOutofBoundsException等异常。


9
这不是正确的答案。迭代器是一种封装数组的方式,这样你就无法编辑它们的内容。而你提到的内容是在后来添加到迭代器中的。 - David
6
所以你使用array.size()作为for循环条件,而不是一个神奇数字。 - camel-man

16

很明显,一个类似于ArrayList的API可以在没有iterator()方法的情况下工作。但是,ArrayList是一个Collection,而iterator()方法是在Collection接口中定义的...因此ArrayList必须实现它。

ArrayList中删除的关键在于通过索引进行删除需要考虑一些问题:

    for (int i = 0; 
         i < a.size(); // Hoist this at your peril
         i++) {
        if (a.get(i) == something) {
            a.remove(i);
            i--;  // Leave this out at your peril
        }
    }

如果你需要在循环中调用一个方法来删除列表元素,情况会变得更糟...因为该方法必须声明它已经删除了一个元素,以便调用者可以调整循环索引。

第三个使用 iterator 的好处是允许你使用 Java 5 的 for (type var : iterable) ... 语法。

底线是你不必在 ArrayList 实例上使用迭代器。如果你不想用,那就不要用。


你可以直接在ArrayList上使用for循环符号;不需要迭代器。只有在遍历过程中删除元素时才需要。如果有一个带有删除标记的ArrayList,在循环结束时自动删除所有标记元素,那就太棒了。但这只是美好的愿望 :) - Rob Grant
除非修改迭代变量被认为是一种不良实践,你永远不应该这样做。 - pseudo
@pseudo - 这是正确的,但我不明白你为什么在这里提到它。我的回答或任何评论是否暗示人们应该修改循环变量? - Stephen C
@RobertGrant:你不能只是倒序遍历数组吗?从尺寸-1开始,直到0结束。 - adam.r
1
@adam.r - 我认为你误解了Robert的评论。倒序遍历数组(列表)没有任何作用,当然也不能解决高效且安全地删除元素的问题。 - Stephen C
显示剩余2条评论

9

这是一个示例,展示了如何以几种不同的方式获得想要的结果。这种冗余不仅存在于Java中。

  • for (int i=0; i < myArray.length; i++) { ... }

这个语法是在Java的早期版本中引入的。它在for { }循环中遍历一个普通的Java数组。这通常是安全的,因为Java数组是固定长度的,所以不可能出现“Index Out of Bounds”异常。

  • for (int i=0; i < myArrayList.size(); i++ { ... }

这个语法反映了Java的一个较新版本,在引入Collections API后引入了ArrayList。实现Collection接口的类(如上面提到的)必须实现一个Iterator,但你不一定要使用它。这个for { }循环没有使用它,但这里的危险在于ArrayLists不是固定大小的。如果它在for循环的主体中缩小了,就会导致异常。

  • for (MyArrayType t : myArrayList) { }

这个语法也是在Java的一个较新版本中发布的。它被称为增强型for循环。任何通过实现Iterable接口提供Iterator的集合类都可以利用这个语法。这允许在不必明确实例化Iterator的情况下遍历集合中的项。在JavaFX应用程序中使用它的一个喜欢的方法是通过一组控件循环来设置属性值,例如重置一组TextField的内容:

for (TextField tf : new TextField[] { txtf1, txtf2, txtf3, txtfa, txtfb, txtfc}) {
    tf.setText("");
}
  • while (myCollectionIterator.hasNext()) { }

你可以明确地实例化迭代器。这在集合大小正在改变时(从集合自己的方法)使用是安全的。可以说迭代器更接近Iterable接口的属性而不是Java语言核心的功能。但由于后来的Java版本,您仍然可以将其用作类似语言的特性(在增强for循环中)。

这些结构提供了冗余,但它们并不完全相同。每个细微差别都使一个在某个特定时间特别有用。应该使用所有这些结构。


感谢您提供有用的信息。 - Karthik Rk

2

Q: 为什么ArrayList需要一个迭代器?

实际上不需要 - 就像你在代码中展示的那样,可以在没有迭代器的情况下遍历和进行核心操作。但这是一个好的功能。

Q: 有人能解释一下迭代器的意义吗?

除了其设计价值外,我认为其中一个重要的特性是它的快速失败特性。我引用ArrayList文档中的这段话:

此类的iterator和listIterator方法返回的迭代器快速失败:如果列表在迭代器创建后的任何时候以任何方式(除了通过迭代器自己的remove或add方法)结构上修改,则迭代器都会抛出ConcurrentModificationException。因此,在并发修改面前,迭代器会快速而干净地失败,而不是在未来的某个不确定的时间冒着任意的、非确定性的行为风险。

如果你想查看代码,你可以在这里看到ArrayList的迭代器实现:ArrayList.java


你的回答中有什么特别之处需要使用这种大字体吗? - Vitaly
搞定了,它变小了。 - Jops

0

针对您的问题,如果我们使用list.remove()方法而不是iterator.remove(),那么将会抛出IndexOutOfBoundsException异常。

如果您在找到要删除的特定对象/索引后放置了break语句,则可以安全地使用list.remove(),这样它将从循环中退出而不会出现任何异常(如IndexOutOfBoundsException)。

即使在同步环境下,以下迭代器代码仍然可能会抛出ConcurrentModificationException异常。

List<String> empNames = new ArrayList<String>();
        synchronized (empNames) {
            Iterator<String> iterator = empNames.iterator();
            while (iterator.hasNext()) {
                iterator.next();
                empNames.add("Another Name"); // throws
                // ConcurrentModificationException
            }
        }

为什么我们使用list.remove()时会出现IndexOutOfBoundsException错误? - JAVA

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接