1. 概述

在处理集合时,我们经常需要遍历其元素。Java为此提供了两个关键接口:IteratorListIterator。尽管它们都服务于相似的目的,但两者之间存在重要的区别,我们需要了解这些区别。

本教程将探讨Java中IteratorListIterator 的差异。

2. Iterator 接口

标准的 Collection 接口继承自 Iterable。此外,Iterable 接口定义了 iterator() 方法,返回一个 Iterator 实例:

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

因此,Iterator 是Java集合框架的基础组成部分,并且适用于所有 Collection 实现,如 ListSet。它允许我们在不知道底层结构的情况下顺序访问集合中的元素。

它提供了三个主要方法:hasNext(), next(), 和 *remove()*:

public interface Iterator<E> {
    
    boolean hasNext();
    E next();   
    default void remove() { throw new UnsupportedOperationException("remove"); }
    ...
}

使用 hasNext()next() 方法,我们可以检查是否有更多元素并移动到下一个元素。

然而,remove() 方法会从集合中移除由 next() 方法返回的最后一个元素。此外,如您所见,remove() 方法是 默认方法,即可选的。其实现取决于底层集合。

让我们创建一个单元测试来覆盖 Iterator 的基本用法:

List<String> inputList = Lists.newArrayList("1", "2", "3", "4", "5");
Iterator<String> it = inputList.iterator();
while (it.hasNext()) {
    String e = it.next();
    if ("3".equals(e) || "5".equals(e)) {
        it.remove();
    }
}

assertEquals(Lists.newArrayList("1", "2", "4"), inputList);

值得注意的是,使用 Iterator 只能单向遍历集合,只能向前移动。

3. ListIterator 接口

ListIteratorIterator 的子类型,因此 Iterator* 提供的所有功能也适用于 *ListIterator

public interface ListIterator<E> extends Iterator<E> {
   
    boolean hasNext();
    E next();
    void remove();

    boolean hasPrevious();
    E previous();
    int nextIndex();
    int previousIndex();
    void set(E e);
    void add(E e);
}

顾名思义,ListIterator 专门用于列表。除了 Iterator 接口的三个方法(hasNext(), next(),remove())之外,ListIterator 接口还提供了一系列新方法,如 previous(), set(), add() 等。

接下来,我们将更深入地了解这些新方法,并理解 IteratorListIterator 之间的区别。

为了简单起见,我们将使用 ArrayList 为例,了解 ListIterator 的用法。

3.1. 前向和后向遍历元素

ListIterator 允许我们在列表的前后两个方向进行遍历。在了解如何操作之前,先理解 ListIterator 的游标位置。

简单来说,ListIterator 的游标并不直接指向元素。对于包含 n 个元素的列表,ListIteratorn+1 种可能的游标位置 (^):

Elements:            Element_0    Element_1    Element_2    Element_3  ... Element_(n-1)
Cursor positions:  ^            ^            ^            ^           ^    ...           ^

ListIteratorprevious()next() 方法返回当前游标位置前后的元素。因此,我们应该注意交替调用 next()previous() 将反复返回相同的元素。理解这个特性对于使用 ListIterator 实现双向遍历至关重要。

下面的示例演示了这一点:

List<String> inputList = Lists.newArrayList("1", "2", "3", "4", "5");
ListIterator<String> lit = inputList.listIterator(); // ^ 1 2 3 4 5
lit.next(); // 1 ^ 2 3 4 5
lit.next(); // 1 2 ^ 3 4 5
 
for (int i = 1; i <= 100; i++) {
    assertEquals("2", lit.previous());
    assertEquals("2", lit.next());
}

如代码所示,在调用两次 next() 后,游标位于 "2" 和 "3" 之间。然后,我们重复调用了 previous()next() 方法共100次。也就是说,它执行了 next() -> previous() -> next() -> previous() -> … 100次。正如我们之前提到的,每次交替调用 next() 和 *previous()*,我们将得到相同的元素。在这种情况下,它是 "2"。我们通过上面的两个断言验证了这一点。

现在,让我们看看如何使用 ListIterator 在两个方向上访问列表元素:

List<String> inputList = Lists.newArrayList("1", "2", "3", "4", "5");
ListIterator<String> lit = inputList.listIterator(); // ^ 1 2 3 4 5

assertFalse(lit.hasPrevious()); // lit is at the beginning of the list
assertEquals(-1, lit.previousIndex());

// forward
assertEquals("1", lit.next()); // after next(): 1 ^ 2 3 4 5
assertEquals("2", lit.next()); // after next(): 1 2 ^ 3 4 5
assertEquals("3", lit.next()); // after next(): 1 2 3 ^ 4 5

// backward
assertTrue(lit.hasPrevious());
assertEquals(2, lit.previousIndex());
assertEquals("3", lit.previous()); // after previous(): 1 2 ^ 3 4 5

assertTrue(lit.hasPrevious());
assertEquals(1, lit.previousIndex());
assertEquals("2", lit.previous()); // after previous(): 1 ^ 2 3 4 5

assertTrue(lit.hasPrevious());
assertEquals(0, lit.previousIndex());
assertEquals("1", lit.previous()); // after previous(): ^ 1 2 3 4 5

如示例所示,我们首先使用 next() 方法向前访问列表中的前三个元素。然后,我们使用 previous() 调用来反向获取这些元素。

在上面的代码中,我们还使用了 previousIndex()ListIterator 的 previousIndex() 返回下一次调用 previous() 将返回的元素的索引。同样,nextIndex() 告诉我们下一次调用 next() 将返回的元素的索引。

3.2. set() 方法

ListIterator 提供了 set() 方法来设置元素的值。Iterator 接口不支持此功能。然而,我们应该注意,ListIterator.set() 将替换由最后一次 next()previous() 调用返回的元素:

List<String> inputList = Lists.newArrayList("1", "2", "3", "4", "5");
ListIterator<String> lit = inputList.listIterator(1); // ^ 1 2 3 4 5
lit.next(); // 1 ^ 2 3 4 5
assertEquals("3", lit.next()); // 1 2 ^ 3 4 5

lit.set("X");
assertEquals(Lists.newArrayList("1", "2", "X", "4", "5"), inputList);

assertEquals("X", lit.previous()); // 1 2 ^ X 4 5

assertEquals("2", lit.previous()); // 1 ^ 2 X 4 5
lit.set("Y");
assertEquals(Lists.newArrayList("1", "Y", "X", "4", "5"), inputList);

如上所示的测试所示,当我们调用 set() 时,最后一次 next()previous() 调用返回的元素被新值替换。

3.3. add() 方法

ListIterator 允许我们在当前游标位置添加元素,遵循以下规则:


Element_x (New)   ^     Element_Y
           |
           ^
           |____ add(New) 

调用 add(NEW) 在当前游标位置之前插入元素,以便后续的 next() 调用不受影响,而后续的 previous() 调用将返回新元素。

一个例子可以说明这一点:

List<String> inputList = Lists.newArrayList("1", "2", "3", "4", "5");
ListIterator<String> lit = inputList.listIterator(); // ^ 1 2 3 4 5
lit.next(); // 1 ^ 2 3 4 5
lit.next(); // 1 2 ^ 3 4 5
lit.next(); // 1 2 3 ^ 4 5

lit.add("X"); // 1 2 3 X ^ 4 5
assertEquals("4", lit.next()); // 1 2 3 X 4 ^ 5; next() isn't affected

lit.previous(); // 1 2 3 X ^ 4 5
lit.previous(); // 1 2 3 ^ X 4 5
lit.previous(); // 1 2 ^ 3 X 4 5
lit.add("Y");   // 1 2 Y ^ 3 X 4 5

assertEquals("Y", lit.previous()); // previous() always return the new element

assertEquals(Lists.newArrayList("1", "2", "Y", "3", "X", "4", "5"), inputList);

4. 总结

在这篇文章中,我们讨论了IteratorListIterator 的用法。现在,让我们总结一下两者的主要区别:

  • Iterator 是通用接口,用于遍历任何集合,而 ListIterator 专用于列表,提供双向遍历。
  • Iterator 只支持向前迭代,使用 next()。相反,ListIterator 支持双向迭代,使用 next() 和 *previous()*。
  • ListIterator 包含额外的方法,如 add() 和 *set()*,用于插入或替换列表元素,而 Iterator 接口没有这些功能。

如往常一样,示例代码的完整源代码可在 GitHub 上找到。