Java Iterator(迭代器)不是一个集合,它是一种用于访问集合的方法,可用于迭代 ArrayList 和 HashSet 等集合。
在我们使用
for(... : ...)
去遍历集合中的所有元素时,其实就隐式地使用了迭代器(Iterator)。
遍历集合的两种方法
通常来说,遍历集合中的元素主要有以下两种方法。
//method1
List<String> list = ...;
for(String str : list) {
System.out.println(str);
}
//method2
List<String> list = ...;
Iterator<String> iter = list.iterator();
while(iter.hasNext()) {
String str = iter.next();
System.out.println(str);
}
对于第二种方法来说,其实是显式地定义了一个Iterator类型的对象,它有iterator()方法,调用该方法可以获取一个迭代器。
迭代器有三个基本操作,分别是 next 、hasNext 和 remove。
调用 it.next() 会返回迭代器的下一个元素,并且更新迭代器的状态。
调用 it.hasNext() 用于检测集合中是否还有元素。
调用 it.remove() 将迭代器返回的元素删除。
Iterator是Java中的一个接口,位于java.util 包中,使用前需要引入它,语法格式如下:
import java.util.Iterator; // 引入 Iterator 类
两种方法的比较
下面,我们来比较一下这两种方法有何不同。
我们先分别运行一下下面的两段代码。
public class Test {
public static void main(String[] args) {
List<String> list = new ArrayList<String>(Arrays.asList("Apple","Orange","Peach"));
for(String str : list) {
System.out.println(str);
}
}
}
public class Test {
public static void main(String[] args) {
List<String> list = new ArrayList<String>(Arrays.asList("Apple","Orange","Peach"));
Iterator<String> iter = list.iterator();
while(iter.hasNext()) {
String str = iter.next();
System.out.println(str);
}
}
}
我们发现它们的运行结果是一样的。这样看它们好像没有什么差别,而且第一种写法要简单一些,代码量更少。
删除元素
下面我们比较一下当从集合中删除元素时,这两种写法的区别。
public class Test {
public static void main(String[] args) {
List<String> list = new ArrayList<String>(Arrays.asList("Apple","Orange","Peach"));
for(String str : list) {
list.remove(str);
}
System.out.println(list);
}
}
当我们运行上面这段代码时,出现了报错。
可以通过根据异常定位到报错的地方:
当我们使用Java中的foreach循环时,其实编译器会根据list对象创建一个Iterator的迭代器。我们对list进行的增删操作的具体实现都必须经过Iterator。在Iterator创建的时候modCount被赋值给了expectedModCount,但是调用list的add和remove方法的时候不会同时自动增减expectedModCount,这样就导致两个count不相等,从而抛出异常。
我们再试试下面这种写法。
public class Test {
public static void main(String[] args) {
List<String> list = new ArrayList<String>(Arrays.asList("Apple","Orange","Peach"));
Iterator<String> iter = list.iterator();
while(iter.hasNext()) {
String str = iter.next();
list.remove(str);
}
System.out.println(list);
}
}
运行之后,出现了和上面一样的报错信息。与上一种写法相比,这里的Iterator被显式地定义出来了,但是同样出现了调用list的remove方法的时候不会同时自动增减expectedModCount的问题,这样就导致两个count不相等,从而抛出异常。
那应该怎样在遍历List的同时删除元素呢,我们看一下下面这种写法。
public class Test {
public static void main(String[] args) {
List<String> list = new ArrayList<String>(Arrays.asList("Apple","Orange","Peach"));
Iterator<String> iter = list.iterator();
while(iter.hasNext()) {
String str = iter.next();
if(str.endsWith("e")) {
iter.remove();
}
}
System.out.println(list);
}
}
最后的运行结果如下:
我们可以看到,这里成功的删除了以e结尾的两个字符串。为什么使用iter.remove()就可以成功删除List中的元素呢?这是因为在Iterator中的remove方法在删除元素后会自动调整迭代器指向的元素,也会自动增减expectedModCount的值,所以不会出现之前的报错。
使用自己的迭代器
上面我们都是使用的Java标准库里的Iterator接口,那能否定义自己的MyIterator类(接口)呢?
答案是肯定的。并且在很多时候,为自己设计的抽象数据类型(ADT)增加一个迭代器是一个很不错的设计模式(Iterator Pattern),用户可以用之前已经非常熟悉的迭代器方法来遍历你设计的ADT中的元素。
在Java的标准库中定义了一个Iterable接口,而实现该接口的集合对象是可遍历迭代的。
这里的Ierator是迭代器的接口,也在Java标准库中有定义。基本结构如下。
而所谓的Iterator pattern就是指让自己的集合类实现Iterable接口,并实现自己独特的Iterator迭代器(重写hasNext, next, remove方法),允许客户端利用这个迭代器进行显式或隐式的迭代遍历。
下面是一个实现样例。
//实现Iterable<E>接口
public class Pair<E> implements Iterable<E> {
private final E first, second;
public Pair(E f, E s) { first = f; second = s; }
//重写iterator方法
//返回自己的迭代器
public Iterator<E> iterator() {
return new PairIterator();
}
//构建自己的Iterator
private class PairIterator implements Iterator<E> {
private boolean seenFirst = false, seenSecond = false;
//Override hasNext方法
public boolean hasNext() { return !seenSecond; }
//Override next方法
public E next() {
if (!seenFirst) { seenFirst = true; return first; }
if (!seenSecond) { seenSecond = true; return second; }
throw new NoSuchElementException();
}
//Override remove方法
//这里不允许删除元素
public void remove() {
throw new UnsupportedOperationException();
}
}
}