ArrayList迭代器源码分析

集合的遍历

Java集合框架中容器有很多种类,如下图中:

对于有索引的List集合可以通过for循环遍历集合:

 1 List<String> list = new ArrayList<>();
 2  3 list.add("aaa");
 4 list.add("bbb");
 5 list.add("ccc");
 6 list.add("ddd");
 7  8 for (int i = 0; i < list.size(); i++) {
 9     System.out.println(list.get(i));
10 }

同样的对于List集合也可以用其迭代器来遍历:

1 Iterator<String> iterator = list.iterator();
2 3 while (iterator.hasNext()) {
4     System.out.println(iterator.next());
5 }

以及使用增强for循环,但是增强for循环还是用迭代器实现的:

1 for (String s : list) {
2     System.out.println(s);
3 }

迭代器接口定义

首先来看下迭代器的接口定义如下:

 1 public interface Iterator<E> {
 2  3     boolean hasNext();
 4  5     E next();
 6  7     default void remove() {
 8         throw new UnsupportedOperationException("remove");
 9     }
10 11     default void forEachRemaining(Consumer<? super E> action) {
12         Objects.requireNonNull(action);
13         while (hasNext())
14             action.accept(next());
15     }
16 }

可以看得到迭代器中只定义了四个方法, hasNext() 判断是否有下个元素, Next() 获取下个元素, remove() 删除元素,以及 forEachRemaining() 方法对每个剩余元素都执行给定操作,直到所有元素都被处理或动作引发异常。

ArrayList迭代器源码分析

下面来看ArrayList中的迭代器实现源码:

 1 private class Itr implements Iterator<E> {
 2     int cursor;       // index of next element to return
 3     int lastRet = -1; // index of last element returned; -1 if no such
 4  5     //判断是否有下个元素就是将cursor与集合size进行比较当cursor不等size时表示有下个元素
 6     //hasNext()方法
 7     public boolean hasNext() {
 8         return cursor != size;
 9     }
10 11     //下个元素
12     public E next() {
13         checkForComodification();
14         //当前的游标赋给i
15         int i = cursor;
16         //判断i是否越界
17         if (i >= size)
18             throw new NoSuchElementException();
19         Object[] elementData = ArrayList.this.elementData;
20         //判断i是否超出elementDate
21         if (i >= elementData.length)
22             throw new ConcurrentModificationException();
23         cursor = i + 1;
24         return (E) elementData[lastRet = i];
25     }
26 27     //删除当前的元素
28     public void remove() {
29         if (lastRet < 0)
30             throw new IllegalStateException();
31         //判断集合版本号与集合迭代器版本号是否相同
32         checkForComodification();
33 34         try {
35             ArrayList.this.remove(lastRet);
36             cursor = lastRet;
37             lastRet = -1;
38             //将删除元素后的集合版本号赋给迭代器版本号
39             expectedModCount = modCount;
40         } catch (IndexOutOfBoundsException ex) {
41             throw new ConcurrentModificationException();
42         }
43     }
44 }

在迭代过程中对集合做出修改就会抛出异常ConcurrentModificationException,这个异常来源是 checkForComodification() 方法,这个方法的代码如下:

1 final void checkForComodification() {
2     if (modCount != expectedModCount)
3     throw new ConcurrentModificationException();
4 }

modCount是当前集合的版本号,每次对集合进行修改操作都会导致modCount加1,expectedModCount是这个集合的迭代器的版本号,在迭代器的初始话过程中可以看到

1 int expectedModCount = modCount;

将集合版本号赋给集合的迭代器的版本号,之后的迭代过程中Next()和hasNext()方法都会调用checkForComodification()这个方法来判断两个版本号是否相同。所以在迭代过程中是不可以对集合进行修改的操作。但是迭代器自带的remove()方法却可以对集合进行元素删除操作,这是因为在迭代器的remove()方法中每次删除元素后都有

1 expectedModCount = modCount;

这个操作来同步集合和集合的迭代器的版本号。因此,使用了迭代器的 remove() 方法对集合中的元素进行了删除操作不会导致ConcurrentModificationException错误。增强for循环是迭代器实现的,因此增强for循环和其同理。在增强for循环中修改元素一样会抛出异常。

增强for循环实现

1 List<String> list = new ArrayList<>();
2 list.add("aaa");
3 list.add("bbb");
4 list.add("ccc");
5 list.add("ddd");
6 
7 for (String s : list) {
8     System.out.println(s);
9 }

上面的代码对list集合用增强for循环进行遍历输出,反编译之后得到如下结果:

 1 List<String> list = new ArrayList();
 2 list.add("aaa");
 3 list.add("bbb");
 4 list.add("ccc");
 5 list.add("ddd");
 6 Iterator var2 = list.iterator();
 7 
 8 while(var2.hasNext()) {
 9     String s = (String)var2.next();
10     System.out.println(s);
11 }

所以可以明显的看到增强for循环实现也是通过迭代器,因此也不能在增强for循环中对集合元素进行修改,否则会抛出ConcurrentModificationException异常。

迭代中多次调用Next()

在如下这种情况中在迭代过程中调用了两次Next()方法:

 1 ArrayList<Integer> list1 = new ArrayList<>();
 2 list1.add(1);
 3 list1.add(2);
 4 list1.add(3);
 5 list1.add(4);
 6 list1.add(5);
 7 Iterator iter = list1.iterator();
 8 
 9 while (iter.hasNext()) {
10 
11     System.out.println(iter.next());  //两次迭代器next()问题
12     System.out.println(iter.next());
13 }

运行结果是可以打印出所有的元素,但是会抛出NoSuchElementException异常,但是如果减少一个list1中的元素就会正常输出:

 1 public static void main(String[] args) {
 2     ArrayList<Integer> list1 = new ArrayList<>();
 3     list1.add(1);
 4     list1.add(2);
 5     list1.add(3);
 6     list1.add(4);
 7     Iterator iter = list1.iterator();
 8 
 9     while (iter.hasNext()) {
10 
11         System.out.println(iter.next());  
12         System.out.println(iter.next());
13     }

运行可以完整输出,并且不会抛出NoSuchElementException异常。这是因为在每次Next()方法中

1 if (i >= elementData.length)
2     throw new ConcurrentModificationException();

都会做如上的判断,来判断当前的游标是否已经在元素地址外,上面的测试代码中出现异常的代码,一共有五个元素,每次while循环都会迭代两次,所以当第三次进入循环的第一次迭代时可以正常输出的,但是第二次迭代就会出现迭代器中的游标已经等于集合的长度,所以会抛出异常。但是在没有抛出异常的代码中,一共四个元素,每次迭代都会调用两次next()方法,所以一共执行了两次while循环,最后一次循环中的最后一次迭代游标正好在 list.length - 1 处,不会抛出异常,并且下一次循环因为hasNext()方法返回false,所以也不会进入while循环中,所以并不会抛出异常。

posted @ 2019-04-18 09:37  Linteresting  阅读(742)  评论(0编辑  收藏  举报