1. 概述
在处理集合时,我们经常需要遍历其元素。Java为此提供了两个关键接口:Iterator 和 ListIterator。尽管它们都服务于相似的目的,但两者之间存在重要的区别,我们需要了解这些区别。
本教程将探讨Java中Iterator 和 ListIterator 的差异。
2. Iterator 接口
标准的 Collection 接口继承自 Iterable。此外,Iterable 接口定义了 iterator() 方法,返回一个 Iterator 实例:
public interface Iterable<T> {
Iterator<T> iterator();
...
}
因此,Iterator 是Java集合框架的基础组成部分,并且适用于所有 Collection 实现,如 List 和 Set。它允许我们在不知道底层结构的情况下顺序访问集合中的元素。
它提供了三个主要方法: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 接口
ListIterator 是 Iterator 的子类型,因此 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() 等。
接下来,我们将更深入地了解这些新方法,并理解 Iterator 和 ListIterator 之间的区别。
为了简单起见,我们将使用 ArrayList 为例,了解 ListIterator 的用法。
3.1. 前向和后向遍历元素
ListIterator 允许我们在列表的前后两个方向进行遍历。在了解如何操作之前,先理解 ListIterator 的游标位置。
简单来说,ListIterator 的游标并不直接指向元素。对于包含 n 个元素的列表,ListIterator 有 n+1 种可能的游标位置 (^):
Elements: Element_0 Element_1 Element_2 Element_3 ... Element_(n-1)
Cursor positions: ^ ^ ^ ^ ^ ... ^
ListIterator 的 previous() 和 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. 总结
在这篇文章中,我们讨论了Iterator 和 ListIterator 的用法。现在,让我们总结一下两者的主要区别:
- Iterator 是通用接口,用于遍历任何集合,而 ListIterator 专用于列表,提供双向遍历。
- Iterator 只支持向前迭代,使用 next()。相反,ListIterator 支持双向迭代,使用 next() 和 *previous()*。
- ListIterator 包含额外的方法,如 add() 和 *set()*,用于插入或替换列表元素,而 Iterator 接口没有这些功能。
如往常一样,示例代码的完整源代码可在 GitHub 上找到。