Iterator和Iterable的区别以及如何使用它们?

2023/06/07

1. 概述

在本教程中,我们将了解Java中Iterable和Iterator接口的用法以及它们之间的区别。

2. 可迭代接口

Iterable接口属于java.lang包。它表示可以迭代的数据结构。

Iterable接口提供了一种生成Iterator的方法。使用Iterable时,我们无法通过索引获取元素。同样,我们也无法从数据结构中获取第一个或最后一个元素。

Java中的所有集合都实现了Iterable接口。

2.1 迭代可迭代对象

我们可以使用增强的for循环(也称为for-each循环)遍历集合中的元素。但是,在这样的语句中只能使用实现Iterable接口的对象。也可以结合使用while语句和Iterator来迭代元素。

让我们看一个使用for-each语句迭代List中的元素的示例:

List<Integer> numbers = getNumbers();
for (Integer number : numbers) {
    System.out.println(number);
}

同样,我们可以将forEach()方法与lambda表达式结合使用:

List<Integer> numbers = getNumbers();
numbers.forEach(System.out::println);

2.2 实现可迭代接口

当我们有想要迭代的自定义数据结构时,Iterable接口的自定义实现可以派上用场。

让我们首先创建一个表示购物车的类,该购物车将在数组中保存元素。我们不会直接在数组上调用for-each循环。相反,我们将实现Iterable接口。我们不希望我们的客户依赖于所选的数据结构。如果我们为客户提供迭代的能力,我们可以轻松地使用不同的数据结构,而客户无需更改代码。

ShoppingCart类实现了Iterable接口并覆盖了它的iterate()方法:

public class ShoppingCart<E> implements Iterable<E> {

    private E[] elementData;
    private int size;

    public void add(E element) {
        ensureCapacity(size + 1);
        elementData[size++] = element;
    }

    @Override
    public Iterator<E> iterator() {
        return new ShoppingCartIterator();
    }
}

add()方法将元素存储在数组中。由于数组的大小和容量是固定的,我们使用ensureCapacity()方法扩展元素的最大数量。

自定义数据结构上的iterator()方法的每次调用都会生成Iterator的新实例。我们创建了一个新实例,因为迭代器负责维护当前的迭代状态。

通过提供iterator()方法的具体实现,我们可以使用增强的for语句来迭代已实现类的对象。

现在,让我们在ShoppingCart类中创建一个内部类来表示我们的自定义迭代器:

public class ShoppingCartIterator implements Iterator<E> {
    int cursor;
    int lastReturned = -1;

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

    public E next() {
        return getNextElement();
    }

    private E getNextElement() {
        int current = cursor;
        exist(current);

        E[] elements = ShoppingCart.this.elementData;
        validate(elements, current);

        cursor = current + 1;
        lastReturned = current;
        return elements[lastReturned];
    }
}

最后,让我们创建一个可迭代类的实例,并在增强的for循环中使用它:

ShoppingCart<Product> shoppingCart  = new ShoppingCart<>();

shoppingCart.add(new Product("Tuna", 42));
shoppingCart.add(new Product("Eggplant", 65));
shoppingCart.add(new Product("Salad", 45));
shoppingCart.add(new Product("Banana", 29));
 
for (Product product : shoppingCart) {
    System.out.println(product.getName());
}

3.迭代器接口

迭代器是Java集合框架的成员。它属于java.util包。该接口允许我们在迭代期间从集合中检索或删除元素。

此外,它还有两个方法可以帮助迭代数据结构并检索其元素——next()和hasNext()。

此外,它还有一个remove()方法,可以删除Iterator指向的当前元素。

最后,forEachRemaining(Consumer<?superE>action)方法对数据结构中的每个剩余元素执行给定的操作。

3.1.遍历集合

让我们看看如何迭代Integer元素列表。在示例中,我们将组合while循环和方法hasNext()和next()。

List接口是Collection的一部分,因此它扩展了Iterable接口。要从集合中获取迭代器,我们只需调用iterator()方法:

List<Integer> numbers = new ArrayList<>();
numbers.add(10);
numbers.add(20);
numbers.add(30);
numbers.add(40);

Iterator<Integer> iterator = numbers.iterator();

此外,我们可以通过调用hasNext()方法检查迭代器是否还有剩余元素。之后,我们可以通过调用next()方法来获取一个元素:

while (iterator.hasNext()) {
    System.out.println(iterator.next());
}

next()方法返回迭代中的下一个元素。另一方面,如果没有这样的元素,它会抛出NoSuchElementException。

3.2.实现迭代器接口

现在,我们将实现Iterator接口。当我们需要使用条件元素检索遍历集合时,自定义实现会很有用。例如,我们可以使用自定义迭代器来迭代奇数或偶数。

为了说明,我们将迭代给定集合中的素数。正如我们所知,如果一个数只能被一个和它本身整除,则它被认为是质数。

首先,让我们创建一个包含数字元素集合的类:

class Numbers {
    private static final List<Integer> NUMBER_LIST = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
}

此外,让我们定义Iterator接口的具体实现:

private static class PrimeIterator implements Iterator<Integer> {

    private int cursor;

    @Override
    public Integer next() {
        exist(cursor);
        return NUMBER_LIST.get(cursor++);
    }

    @Override
    public boolean hasNext() {
        if (cursor > NUMBER_LIST.size()) {
            return false;
        }

        for (int i = cursor; i < NUMBER_LIST.size(); i++) {
            if (isPrime(NUMBER_LIST.get(i))) {
                cursor = i;
                return true;
            }
        }

        return false;
    }
}

具体实现通常创建为内部类。此外,他们还负责维护当前的迭代状态。在上面的示例中,我们将下一个素数的当前位置存储在实例变量中。每次我们调用next()方法时,变量都会包含即将到来的素数的索引。

next()方法的任何实现都应该在没有更多元素剩余时抛出NoSuchElementException异常。否则,迭代可能会导致意外行为

让我们在Number类中定义一个方法,它返回PrimeIterator类的一个新实例:

public static Iterator<Integer> iterator() {
    return new PrimeIterator();
}

最后,我们可以在while语句中使用自定义迭代器:

Iterator<Integer> iterator = Numbers.iterator();
 
while (iterator.hasNext()) {
   System.out.println(iterator.next());
}

4. Iterable和Iterator的区别

综上所述,下表显示了Iterable和Iterator接口之间的主要区别:

可迭代的 迭代器
表示可以使用for-each循环迭代的集合 表示可用于迭代集合的接口
实现Iterable时,我们需要覆盖iterator()方法 实现Iterator时,我们需要覆盖hasNext()和next()方法
不存储迭代状态 存储迭代状态
不允许在迭代期间删除元素 允许在迭代期间删除元素

5.总结

在本文中,我们了解了Java中Iterable和Iterator接口的区别及其用法。

与往常一样,本教程的完整源代码可在GitHub上获得。

Show Disqus Comments

Post Directory

扫码关注公众号:Taketoday
发送 290992
即可立即永久解锁本站全部文章