阿里java开发手册已经发表,很多都值得认真研究思考,看到零度的思考题,没忍住研究了一下。

首先,看一下给出的反例的执行结果。
1. 如果是"1",最后list中的元素为["2"]
2. 如果把"1"换成"2",会抛出ConcurrentModificationException异常
为什么会出现这种情况?这就要考察foreach的执行过程了。
 

1. 代码编译
foreach其实是一种语法糖,通过简单明了的java语法,实现相对复杂的功能,通过查看代码编译之后的字节码文件,可以看到,foreach的循环会在编译期被转为迭代器(Iterator)的方式,以下是反编译之后的代码

List<String> list = Lists.newArrayList();
list.add("1");
list.add("2");
Iterator var2 = list.iterator();
while(var2.hasNext()) {
String num = (String)var2.next();
if("2".equals(num)) {
list.remove(num);
}
}

2. ArrayList中的Iterator考察

在ArrayList内部有一个Itr的内部类,该内部类实现了Iterator接口,通过ArrayList的iterator()函数获取到的就是内部类Itr的实例对象。
 
内部类Itr的属性说明
cursor:下一个返回元素的索引
lastRet:上一个返回元素的索引,如果没有,就是默认值-1
expectedModCount:默认值是modCount(是AbstractList的属性,表示集合结构发生变化的次数,每次add或remove都会加1),从变量定义上就可以看出它是一个期望值,用于在遍历的过程中查看ArrayList的结构有没有发生变化,有点类似CAS的做法

内部类Itr的主要函数说明

hasNext()函数

用于判断是否遍历到了集合的末尾

return cursor != size;

checkForComodification()函数

在next()和remove()函数中都会首先调用该方法来判断集合的结构是否发生变化,如果结构发生了变化,modCount就会加1,不等于expectedModCount,就会抛出异常

if (modCount != expectedModCount)
throw new ConcurrentModificationException();
 
next()函数

1. 先检查集合的结构有没有发生变化,若是,则抛出异常
2. 判断cursor有没有超过集合元素的个数
3. 判断cursor有没有超过ArrayList底层数组结构的大小,若是,则抛出异常
4. cursor加1,lastRet设置为当前返回元素的索引

checkForComodification();
int i = cursor;
if (i >= size)
throw new NoSuchElementException();
Object[] elementData = ArrayList.this.elementData;
if (i >= elementData.length)
throw new ConcurrentModificationException();
cursor = i + 1;
return (E) elementData[lastRet = i];

remove()函数

1. lastRet判断是否小于0,若是,表示还未开始遍历集合,迭代器当前的索引位于集合第一个元素之前
2. 判断元素的结构有没有发生变化
3. 通过ArrayList的remove函数去除lastRet索引位置的元素,此时modCount加1
4. cursor回退到lastRet的索引位置,lastRet设为-1,expectedModCount设置为当前modCount值
if (lastRet < 0)
throw new IllegalStateException();
checkForComodification();
try {
ArrayList.this.remove(lastRet);
cursor = lastRet;
lastRet = -1;
expectedModCount = modCount;
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}

3. 结合代码分析反例

条件为"1".equals(num)的情况
第一次循环,使得cursor为1,此时,符合判断条件,调用集合的remove函数删除元素,modCount加1,此时不等于expectedModCount,size减1变为1
第二次循环,调用hasNext函数,此时cursor和size都为1,判断集合中没有可遍历的元素,遍历到了末尾,结束循环,集合中为"2"的元素是没有遍历到的
最终打印出集合,结果显示为["2"]
 
条件为"2".equals(num)的情况
第一次循环,使得cursor为1,此时,不符合判断条件
第二次循环,hasNext调用后发现集合中还有元素,继续遍历,cursor为2,此时,符合判断条件,调用集合的remove函数删除元素,modCount加1,此时不等于expectedModCount,size减1变为1
第三次循环,hasNext函数调用的时候,cursor为2,大于size,两者也不相等,返回true,继续执行循环体,此时,会调用next函数,由于next函数首先会判断集合的结构有没有发生变化,因为第二次循环中,集合的结构已经变化了,因此会抛出ConcurrentModificationException异常

4. 为什么正例就不会出现这种问题

因为删除元素调用的是迭代器的remove函数,size变化的同时,cursor也发生了变化,不会出现cursor大于size的情况,同时,集合结构发生变化之后,迭代器的remove函数中对expectedModCount重新设值,感知到了结构的变化
 
最后,并发操作,需要对迭代器加锁,就不在此赘述了。

对foreach循环的思考的更多相关文章

  1. 为什么阿里巴巴禁止在 foreach 循环里进行元素的 remove/add 操作--java.util.ConcurrentModificationException

    摘要 foreach循环(Foreach loop)是计算机编程语言中的一种控制流程语句,通常用来循环遍历数组或集合中的元素. 在阿里巴巴Java开发手册中,有这样一条规定: 但是手册中并没有给出具体 ...

  2. 有关集合的foreach循环里的add/remove

    转自:Hollis(微信号:hollischuang) 在阿里巴巴Java开发手册中,有这样一条规定: 但是手册中并没有给出具体原因,本文就来深入分析一下该规定背后的思考. 1 .foreach循环 ...

  3. JAVA中的for-each循环与迭代

    在学习java中的collection时注意到,collection层次的根接口Collection实现了Iterable<T>接口(位于java.lang包中),实现这个接口允许对象成为 ...

  4. foreach循环 Java

    第一次遇到foreach循环,是在PHP的数组中,同样,在Java数组中,也遇到了foreach循环,都是用来遍历数组(集合).遍历数组,首先想到的一般都是用while,do while,for循环, ...

  5. 集合框架遍历方式之——for-each循环

    从Java5起,在Java中有了for-each循环,可以用来循环遍历collection和array.Foreach循环允许你在无需保持传统for循环中的索引,或在使用iterator /ListI ...

  6. Java语法糖1:可变长度参数以及foreach循环原理

    语法糖 接下来几篇文章要开启一个Java语法糖系列,所以首先讲讲什么是语法糖.语法糖是一种几乎每种语言或多或少都提供过的一些方便程序员开发代码的语法,它只是编译器实现的一些小把戏罢了,编译期间以特定的 ...

  7. 巧用array_map()和array_reduce()替代foreach循环

    1.array_reduce( $arr , callable $callback ) 使用回调函数迭代地将数组简化为单一的值. 其中$arr 为输入数组,$callback($result , $v ...

  8. For-Each循环

    For-Each循环也叫增强型的for循环,或者叫foreach循环. For-Each循环是JDK5.0的新特性(其他新特性比如泛型.自动装箱等). For-Each循环的加入简化了集合的遍历. 语 ...

  9. php学习笔记:foreach循环访问关联数组里的值

    foreach循环可以将数组里的所有值都访问到,下面我们展示下,用foreach循环访问关联数组里的值. 例如: $fruit=array('apple'=>"苹果",'ba ...

随机推荐

  1. rpm包管理

    库文件 linux上,库文件是非常重要的,因为很多的软件都不是将所有的自己在需要的函数库自己写好,而是将一部分自己软件特有的库文件自己写,通用的库文件全部动态链接到公共库上去,这样不仅节省空间,同时用 ...

  2. SVM原理以及Tensorflow 实现SVM分类(附代码)

    1.1. SVM介绍 1.2. 工作原理 1.2.1. 几何间隔和函数间隔 1.2.2. 最大化间隔 - 1.2.2.0.0.1. \(L( {x}^*)\)对$ {x}^*$求导为0 - 1.2.2 ...

  3. Spring第八篇【XML、注解实现事务控制】

    前言 本博文主要讲解Spring的事务控制,如何使用Spring来对程序进行事务控制-. 一般地,我们事务控制都是在service层做的..为什么是在service层而不是在dao层呢??有没有这样的 ...

  4. 分页复用代码【Page类、JSP显示页面】

    前言 为了复用,记载一些以前写过的工具类.方法 page类 import java.util.List; /** * Created by ozc on 2017/3/1. */ public cla ...

  5. 框架应用:Mybatis (三) - 关系映射

    你有一张你自己的学生证?(一对一) 你这一年级有多少个学生?(一对多) 班上学生各选了什么课?(多对多) 两张表以上的操作都需要联立多张表,而用SQL语句可以直接联立两张表,用工程语言则需要手动完成这 ...

  6. 改用Struts2.5 出现404 错误

    这个是因为现在Struts 使用  2.5   只需要8 个 jar 包 Xworks-core 已经加到 struts-core中了 原因 是  Jar 包 冲突  导致 的    其次 如果 使用 ...

  7. Bootstrap框架的了解和使用之栅格系统

       前    言 Bootstrap Bootstrap 包含了一个响应式的.移动设备优先的.不固定的网格系统,可以随着设备或视口大小的增加而适当地扩展到 12 列.它包含了用于简单的布局选项的预定 ...

  8. Qt全局宏和变量

    1.  Qt 全局宏定义 Qt版本号: QT_VERSION :  (major << 16) + (minor << 8) + patch 检测版本号: QT_VERSION ...

  9. netty4.x 传输文件

    一:简介 netty传输文件的例子并不多,当前的项目刚才需要使用netty,所以就记录一下使用方法,使用netty传输文件,首先需要启动一个服务端,等待服务端请求监听,然后传输文件的时候,启动一个客户 ...

  10. 大牛教你用3行HTML代码卡死一台机器

    前言 学习web渗透测试等安全工作的朋友们,想必大部分接触的最早的就是HTML了. 其实学过html的朋友们都知道,html中可以插入JavaScript代码,而对于JavaScript代码,这里就不 ...