集合的遍历

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. 常用的 jQuery 事件

    $(document).ready(): $(document).ready() 方法允许我们在文档完全加载完后执行函数. click(): click() 方法是当按钮点击事件被触发时会调用一个函数 ...

  2. 原生js标识当前导航位置(给当前导航一个className=active)

    导航html结构为: <div class="header2-nav"> <a href="index.html">首页</a&g ...

  3. 2019年 Gratner数据分析平台对比 - PowerBI大幅领先

    先睹为快,看看你正在用的工具在哪里? 文末见2017-2018图 对比2019年, 1.ThoughtSpot好像发展很快 2.IBM...... 3.Microstrategy好像表现还不错 4.L ...

  4. python 使用 PIL 和 matplotlib 获取图片像素点处理之后再写入

    python 版本 3.x 首先安装 PIL 由于PIL仅支持到Python 2.7,加上年久失修,于是一群志愿者在PIL的基础上创建了兼容的版本,名字叫Pillow,支持最新Python 3.x,又 ...

  5. mysql-5.7.25 源码 安装

    mysql-5.7.25 源码 安装 编译 export INSTALL_PREFIX="/data/services" export MYSQL_INSTALL_PATH=&qu ...

  6. 2000w数据,redis中只存放20w的数据,如何保证redis中的数据都是热点数据

    redis 内存数据集大小上升到一定大小的时候,就会施行数据淘汰策略. redis 提供 6种数据淘汰策略:voltile-lru:从已设置过期时间的数据集(server.db[i].expires) ...

  7. 测试客户端连接12c ASM实例

    环境:Oracle 12.2.0.1 RAC 背景:用户反映12c ASM创建的用户具备sysasm权限,但无法在客户端连接到ASM实例,且没有报错. 1.ASM实例创建用户赋予sysasm权限 2. ...

  8. python查询数据库返回数据

    python查询数据库返回数据主要运用到flask框架,pymysql 和 json‘插件’ #!/usr/bin/python # -*- coding: UTF-8 -*- import pymy ...

  9. Vue渐进式JavaScript 框架

    1. Vue简介 1.1  初步了解Vue.js框架 Vue.js (读音 /vjuː/,类似于 view) 是一种轻量级前端MVVM框架.同时吸收了React(组件化)和Angular(灵活指令页面 ...

  10. Install Sudo for Debian

    $ su $ apt-get install sudo $ vim /etc/sudoers 1 2 3 在文本中添加: "username" ALL=(ALL) ALL 1 保存 ...