目录
3.3 while 循环中,一次只能调用一次 next() 方法
1. 了解迭代器 Iterator
迭代器就是我们 Java 中常说的 Iterator,它是一个接口,如下图所示,
在 Iterator 接口中,有两个非常重要的方法,hasNext()和next(),它是迭代器的核心方法。
hasNext() 方法的作用是判断当前位置是否有元素,返回值为布尔类型;
next() 方法的作用是获取当前元素,并将迭代器移动到下一个位置,返回值就是集合中的元素对象;
迭代器最重要的作用就是遍历集合中的元素;
Iterator 接口有好几个实现类,有的实现类还是其他集合中的内部类,这里以 ArrayList 集合为例,ArrayList 集合中就有 iterator 这一方法,如下图所示
该方法中直接返回了一个 Itr 对象,我们点击 Itr 跟进查看,如下
在这里我们就可以看到 Itr 是 Iterator 接口的一个实现类。
2. 迭代器使用示范
根据下方代码,我创建一个数组并添加元素,获取迭代器对象
public class IteratorTest {
public static void main(String[] args) {
// 初始化一个数组
List<String> list = new ArrayList<String>(16);
list.add(0,"三");
list.add(1,"连");
list.add(2,"外");
list.add(3,"加");
list.add(4,"转");
list.add(5,"发");
list.add(6,"了");
list.add(7,"吗");
list.add(8,"?");
list.add(9,"?");
// 获取迭代器对象
Iterator<String> iterator = list.iterator();
}
}
迭代器初始创建的时候,如下图,它就是默认指向第一个元素的位置;
下面,我们就可以通过迭代器遍历元素了,这个时候就会用到上方我说道的两个方法,hasNext()和next(),我们可以采用 while 循环,代码如下
public class IteratorTest {
public static void main(String[] args) {
// 初始化一个数组
List<String> list = new ArrayList<String>();
list.add(0,"三");
list.add(1,"连");
list.add(2,"外");
list.add(3,"加");
list.add(4,"转");
list.add(5,"发");
list.add(6,"了");
list.add(7,"吗");
list.add(8,"?");
list.add(9,"?");
// 获取迭代器对象
Iterator<String> iterator = list.iterator();
// hasNext() 方法返回值为布尔类型,将 iterator.hasNext() 直接作为循环条件,
// 只要有值,就会一直循环,直到遍历到最后一个值
while (iterator.hasNext()){
// next() 方法获取当前元素,并将指针移到下一个元素的位置
String str = iterator.next();
// 定义字符串变量接受当前元素并打印输出
System.out.println(str);
}
}
}
遍历到索引9的位置之后,iterator 又向下移动一位,此时索引9后面已经没有元素了,所以
iterator.hasNext() 返回 fasle,循环结束
我们也可以在控制台得到结果如下所示
3. 迭代器使用时需要注意的细节
3.1 迭代器不会报索引越界异常
如果迭代器已经迭代完成,继续调用 next() 方法会爆 NoSuchElementException(没有这个元素异常),不会报索引越界异常;
因为迭代器 Iterator 本身是不依赖于索引的,所以 Iterator 不仅在有序集合 List 中可以使用,也可以在无序 Set 中使用。因此它并不会报索引越界异常,这一点要记住。
如下所示,我们在刚才的代码后面继续调用 next() 方法,控制台得出的结果如下所示
3.2 迭代器不会自动复位
迭代器完成迭代之后,指针不会复位,也可以简单的理解为是一次性的,如果我们还想遍历第二遍集合,只能重新获取一个迭代器对象,再从新开始遍历集合;
3.3 while 循环中,一次只能调用一次 next() 方法
这个需要我们注意的是,如果我的集合中的元素是偶数个数时,调用两次 next() 并不会报错,但如果我的集合中的元素是奇数个数时,调用两次 next() 就会报错;
这一点很好解释,如下代码,我向集合中添加了四个元素,一次循环执行两次 next() 方法
public static void main(String[] args) {
// 初始化一个数组
List<String> list = new ArrayList<String>();
list.add(0,"aaa");
list.add(1,"bbb");
list.add(2,"ccc");
list.add(3,"ddd");
// 获取迭代器对象
Iterator<String> iterator = list.iterator();
// hasNext() 方法返回值为布尔类型,将 iterator.hasNext() 直接作为循环条件,
// 只要有值,就会一直循环,直到遍历到最后一个值
while (iterator.hasNext()){
// next() 方法获取当前元素,并将指针移到下一个元素的位置
String str = iterator.next();
System.out.println(str);
String str2 = iterator.next();
// 定义字符串变量接受当前元素并打印输出
System.out.println(str2);
}
}
执行如下,正常输出
当我再添加一个元素 eee 时,执行 main 方法,就会报 NoSuchElementException(没有这个元素异常),如下图所示
很好解释,因为我们一次循环执行两次 next(),那么指针也会移动两次。在第三次循环的时候,第一次 next() 取出的是第五个元素 eee ,也就是最后一个元素,然后将指针向下移动一位。此时后面已经指向了没有数据的位置,那么第二次调用 next() 方法它获取元素时就获取不到了,就会爆出 NoSuchElementException(没有这个元素异常)。
3.4 迭代器遍历集合时,不能用集合的方法进行增加或删除
这里我们看源码即可得知,Java中 AbstractList 类中维护了 modCount 这一变量,默认为0。
然后我们看 remove(int index) 根据索引删除元素的方法,一旦调用这个方法,就会执行 modCount++
在remove(Object o) 根据对象删除方法中也是一样的,调用该方法 modeCount++
add()方法也是一样的,调用 add() 方法之后,内部维护的 modCount 就会加一,这里我就不展示了,有兴趣的同学看源码就可以知道
总而言之,言而总之,也就是说 modCount 实际上是在记录我们创建的数组修改的次数。
重点来啦!!!
如下所示,Itr 是 ArrayList 的一个内部类,它实现了 Iterator 迭代器接口,我画线的 expectedModCount 翻译过来意为期望修改次数,modCount 赋值给了 expectedModCount,也就是说,当我们创建了一个数组的 iterator 对象之后,当前数组的修改次数 modCount 的值就会赋值给 excepectedModCount。
该类中也实现了 Iterator 接口中的 next() 方法,如下所示,我们的 Iterator 对象每次执行 next() 操作的时候,都会执行 checkForComdification() 方法,如下所示
我们点击跟进该方法查看方法体,这里我们看到,在该方法中,他做了判断,expectedModCount 期望修改值 是否与创建 iterator 对象时赋予的值相同,如果不同,说明创建iterator 之后数组被添加或删除过元素,然后就会抛出异常。
总结来说:就是集合的内部维护了一个 modCount 变量用来记录数组修改过的次数,当我们创建了迭代器要去遍历数组时,modCount 变量的值就会传递给 Itr 内部迭代器类的其中一个变量,当我们在利用迭代器遍历的过程中,每次遍历一个元素都会去执行一次 checkForComdification() 方法,来判断当前数组的修改次数是否与刚创建迭代器对象时赋予的值相同,如果相同说明在迭代期间没有人修改过数组,如果不相同则说明我迭代器在迭代数组的过程中有其他人对数组进行过操作,就会爆出 ConcurrentModificationException(并发修改异常)。