2. Iterable接口
Iterable
接口位于java.lang
包下,它表示一个可被遍历的数据结构。该接口提供一个生成Iterator
的方法。使用Iterable
时无法通过索引获取元素,也无法直接获取首尾元素。
Java中所有集合类都实现了Iterable
接口。
2.1 遍历Iterable
我们可以使用增强for循环(即for-each循环)遍历集合元素,但只有实现Iterable
接口的对象才能用于这种语句。也可以结合Iterator
和while语句遍历。
下面是用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接口
当需要遍历自定义数据结构时,实现Iterable
接口会很有用。
创建一个表示购物车的类,内部用数组存储元素。我们不直接暴露数组给客户端遍历,而是实现Iterable
接口——这样客户端代码不依赖具体数据结构,后续更换实现时无需修改客户端代码。
ShoppingCart
类实现Iterable
接口并重写iterator()
方法:
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语句遍历该类的对象。
现在创建内部类ShoppingCartIterator
:
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. Iterator接口
Iterator
是Java集合框架的成员,位于java.util
包。该接口允许在遍历过程中获取或移除集合元素。
它包含两个核心方法用于遍历和获取元素:next()
和hasNext()
。此外还有:
remove()
:移除当前指向的元素forEachRemaining(Consumer<? super E> action)
:对剩余元素执行指定操作
3.1 遍历集合
下面演示如何遍历List<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;
}
}
实现要点:
- 通常作为内部类实现
- 实例变量
cursor
保存下一个质数的索引- 每次
next()
调用后更新索引
**任何next()
实现必须在无元素时抛出NoSuchElementException
**,否则可能导致意外行为。
在Numbers
类中添加获取迭代器的方法:
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遍历的集合 | 提供遍历集合的能力 |
实现方法 | 需重写iterator() 方法 |
需重写hasNext() 和next() 方法 |
状态维护 | 不存储遍历状态 | 存储当前遍历状态 |
元素移除 | 遍历过程中不允许移除元素 | 允许通过remove() 移除当前元素 |
5. 总结
本文深入探讨了Java中Iterable
与Iterator
接口的核心差异及使用场景。关键要点:
- Iterable:让集合支持for-each循环,隐藏内部实现
- Iterator:提供细粒度的遍历控制,支持条件遍历和元素移除
- 协作关系:Iterable通过iterator()方法生成Iterator实例
简单粗暴地说:Iterable是"可遍历"的声明,Iterator是"怎么遍历"的实现。两者配合使用,既保证代码简洁性又提供遍历灵活性。
示例代码已上传至GitHub仓库。