集合的遍历

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

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

 List<String> list = new ArrayList<>();

list.add("aaa");
list.add("bbb");
list.add("ccc");
list.add("ddd");

for (int i = 0; i < list.size(); i++) {
System.out.println(list.get(i));
}

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

 Iterator<String> iterator = list.iterator();

while (iterator.hasNext()) {
System.out.println(iterator.next());
}

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

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

迭代器接口定义

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

 public interface Iterator<E> {

boolean hasNext();

E next();

default void remove() {
throw new UnsupportedOperationException("remove");
}

default void forEachRemaining(Consumer<? super E> action) {
Objects.requireNonNull(action);
while (hasNext())
action.accept(next());
}
}

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

ArrayList迭代器源码分析

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

 private class Itr implements Iterator<E> {
int cursor; // index of next element to return
int lastRet = -1; // index of last element returned; -1 if no such

//判断是否有下个元素就是将cursor与集合size进行比较当cursor不等size时表示有下个元素
//hasNext()方法
public boolean hasNext() {
return cursor != size;
}

//下个元素
public E next() {
checkForComodification();
//当前的游标赋给i
int i = cursor;
//判断i是否越界
if (i >= size)
throw new NoSuchElementException();
Object[] elementData = ArrayList.this.elementData;
//判断i是否超出elementDate
if (i >= elementData.length)
throw new ConcurrentModificationException();
cursor = i + 1;
return (E) elementData[lastRet = i];
}

//删除当前的元素
public void remove() {
if (lastRet < 0)
throw new IllegalStateException();
//判断集合版本号与集合迭代器版本号是否相同
checkForComodification();

try {
ArrayList.this.remove(lastRet);
cursor = lastRet;
lastRet = -1;
//将删除元素后的集合版本号赋给迭代器版本号
expectedModCount = modCount;
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
}

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

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

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

 int expectedModCount = modCount;

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

 expectedModCount = modCount;

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

增强for循环实现

 List<String> list = new ArrayList<>();
list.add("aaa");
list.add("bbb");
list.add("ccc");
list.add("ddd"); for (String s : list) {
System.out.println(s);
}

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

 List<String> list = new ArrayList();
list.add("aaa");
list.add("bbb");
list.add("ccc");
list.add("ddd");
Iterator var2 = list.iterator(); while(var2.hasNext()) {
String s = (String)var2.next();
System.out.println(s);
}

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

迭代中多次调用Next()

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

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

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

 public static void main(String[] args) {
ArrayList<Integer> list1 = new ArrayList<>();
list1.add(1);
list1.add(2);
list1.add(3);
list1.add(4);
Iterator iter = list1.iterator(); while (iter.hasNext()) { System.out.println(iter.next());
System.out.println(iter.next());
}

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

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

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

ArrayList迭代器源码分析的更多相关文章

  1. ArrayList的源码分析

    在项目中经常会用到list集合来存储数据,而其中ArrayList是用的最多的的一个集合,这篇博文主要简单介绍ArrayList的源码分析,基于JDK1.7: 这里主要介绍 集合 的属性,构造器,和方 ...

  2. 数据结构——ArrayList的源码分析(你所有的疑问,都会被解答)

    一.首先来看一下ArrayList的类图: 1,实现了RandomAccess接口,可以达到随机访问的效果. 2,实现了Serializable接口,可以用来序列化或者反序列化. 3,实现了List接 ...

  3. ArrayList实现源码分析

    本文将以以下几个问题来探讨ArrayList的源码实现 1.ArrayList的大小是如何自动增加的 2.什么情况下你会使用ArrayList?什么时候你会选择LinkedList? 3.如何复制某个 ...

  4. Java——ArrayList底层源码分析

    1.简介 ArrayList 是最常用的 List 实现类,内部是通过数组实现的,它允许对元素进行快速随机访问.数组的缺点是每个元素之间不能有间隔, 当数组大小不满足时需要增加存储能力,就要将已经有数 ...

  5. ArrayList方法源码分析

    本文将从ArrayList类的存储结构.初始化.增删数据.扩容处理以及元素迭代等几个方面,分析该类常用方法的源码. 数据存储设计 该类用一个Object类型的数组存储容器的元素.对于容量为空的情况,提 ...

  6. 集合之ArrayList的源码分析

    转载请注明出处 一.介绍 对于ArrayList,可以说诸位绝不陌生,可以说是在诸多集合中运用的最多一个类之一,那么它是怎样构成,怎样实现的呢,相信很多人都知道数组构成的,没毛病,如果遇到面试的时候, ...

  7. 迎难而上ArrayList,源码分析走一波

    先看再点赞,给自己一点思考的时间,思考过后请毫不犹豫微信搜索[沉默王二],关注这个长发飘飘却靠才华苟且的程序员.本文 GitHub github.com/itwanger 已收录,里面还有技术大佬整理 ...

  8. ArrayList<E>源码分析

    ArrayList是按照线性表结构实现的 ArrayList的主要继承结构 public class ArrayList<E> extends AbstractList<E> ...

  9. ArrayList构造方法源码分析

    首先看一下无参的构造方法: private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}; transient Object ...

随机推荐

  1. 【托业】【跨栏】TEST06

    26-30 26 27 28 28 29 30

  2. 前端 CSS 目录

    前端 CSS 介绍 前端 CSS语法 前端 CSS 注释

  3. TCP/IP协议 网络层

    IP协议介绍 1.IP协议是TCP/IP协议族中最为核心的协议.IP协议将多个包交换网络连接起来,它在源地址和目的地址之间传送一种称为数据包的东西,它还提供对数据大小的重新组装功能,以适应不同网络对包 ...

  4. Hello greenDAO(SQLite)

    一.配置Gradle Scripts: 1.1.build.gradle(Project:*****) buildscript { repositories { google() jcenter() ...

  5. Adaboost总结

    一.简介 Boosting 是一类算法的总称,这类算法的特点是通过训练若干弱分类器,然后将弱分类器组合成强分类器进行分类.为什么要这样做呢?因为弱分类器训练起来很容易,将弱分类器集成起来,往往可以得到 ...

  6. springmvc中的一些服务器报错

    这是springmvc的前端控制器的加载格式要求.服务器加载web.xml后,它会在web-inf/下找名为spring[servlet名/handler处理器名]-servlet.xml文件. 可以 ...

  7. linux----------yum一些安装命令汇总

    1.yum install -y psmisc        安装killall命令 2.yum install -y lrzsz        安装sz(下载)和rz(上传)命令 3.yum ins ...

  8. validationEngine 使用

    引入文件 环境在 jQuery 下 , 所有先要引入 jQuery <%--校验样式--%> <link rel="stylesheet" href=" ...

  9. UIElementImageShot

    MemoryStream memStream = new MemoryStream(); System.Windows.Media.Imaging.RenderTargetBitmap bmp = n ...

  10. 开发宏功能:excel中从sheet批量插入

    源数据如图: 宏操作: 生成数据后: 关键操作:在excel中启用开发工具,添加宏,然后添加模块即可,编辑完代码后,自定义功能按钮即可. Sub MakeDataSource() Dim isExis ...