集合的遍历

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. CF1033G Chip Game

    题意 给你一个长度为\(n\)的序列和一个数\(m\). 小A和小B分别在\([1,m]\)选出一个数\(a\)和\(b\),然后开始游戏. 轮到小A时,他选择一个元素减\(a\):小B则选择一个元素 ...

  2. mysql8.0.13免安装版的安装配置详解

    一.下载 下载地址:https://dev.mysql.com/downloads/mysql/ 二.解压到某个目录,例如:D:/mysql/mysql-8.0.13-winx64 三.配置环境变量 ...

  3. HTML+CSS+JavaScript-案例

    CSS-flex弹性布局案例1: HTML代码: <!DOCTYPE html> <html lang="en"> <head> <met ...

  4. luogu4643 [国家集训队]阿狸和桃子的游戏

    题目链接:洛谷 这道题乍一看非常的难,而且题目标题上的标签让人很害怕. 但其实这道题并不难写(只要想到了...emm) 因为我们只需要知道两个人得分之差,所以我们可以对条件进行变换. 我们将边权平分到 ...

  5. 【托业】【跨栏】TEST05

    22 23 21. 22 23 24 25 REVIEW TEST05

  6. spring-boot logback配置

    接着上篇的代码,日志在不同环境下的配置也不一样,所以要分开配置,主要使用maven的profile 1.1 在pom.xml中添加 <profiles> <profile> & ...

  7. Ch07 包和引入 - 练习

    1. 编写示例程序,展示为什么  package com.horstmann.impatient  不同于 package com package horstmann package impatien ...

  8. yum 运行失败

    https://stackoverflow.com/questions/47633870/rpm-lib64-liblzma-so-5-version-xz-5-1-2alpha-not-found- ...

  9. 2019-oo-第二次总结

    这一单元是关于模拟电梯运行,考验多线程的一个单元,难度由简入入深,从多线程单部电梯,到优化,再到多线程多部电梯,难度一次次的提高. 一.多线程单部电梯(傻瓜调度) 1.设计策略 这一次我只额外的开了一 ...

  10. js某一元素在数组中的索引

    第一种:数组遍历 function search(arr,dst){ var i = arr.length; while(i-=1){ if (arr[i] == dst){ return i; } ...