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循环中,所以并不会抛出异常。