阿里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. 数据库Mysql的安装及操作---数据引擎

    一.1.什么是数据           描述事物的符号记录称为数据.       2.什么是数据库         存放数据的仓库,只不过这个仓库在计算机上存储设备上. 二.Mysql的介绍     ...

  2. Exception in thread "main" org.hibernate.TransientObjectException: object references an unsaved tran

    今天在使用一对多,多对一保存数据的时候出现了这个错误 Hibernate错误: Exception in thread "main" org.hibernate.Transient ...

  3. Ant-打增量包

    如何打增量包具体用法见如下百度网盘的文档. http://pan.baidu.com/s/1gd5pAp1 1, ant 打源码增量包 build_incremental_src.xml 内容如下: ...

  4. 走进AngularJS

      前  言 xiaoq AngularJS 通过新的属性和表达式扩展了 HTML. 使用起来非常方便. 1. AngularJS的指令与表达式 AngularJS 通过 指令 扩展了 HTML,且通 ...

  5. js 操作数组(过滤对应数据)

    过滤掉相应数据 var fileList = { "85968439868a92": [{name: 'food.jpeg'}, {name: 'ood.jpeg'}], &quo ...

  6. 深入理解计算机系统chapter5

    编写高效的程序需要:1.选择合适的数据结构和算法 2.编译器能够有效优化以转换为高效可执行代码的源代码 3.利用并行性 优化编译器的局限性 程序示例: combine3的汇编代码: load-> ...

  7. 第4章 同步控制 Synchronization ----信号量(Semaphore)

    许多文件中都会提到 semaphores(信号量),因为在电脑科学中它是最具历史的同步机制.它可以让你陷入理论的泥淖之中,教授们则喜欢问你一些有关于信号量的疑难杂 症.你可能不容易找到一些关于 sem ...

  8. 实例讲解webpack的基本使用第一篇

    更新文章不容易,尤其是更新高质量的文章更加不易,因此为了节约时间,闲话就不多说了.关于webpack的介绍,webpack是用来干嘛的,这些基础概念性的东西,就不在此赘述,下面直接开始正题. webp ...

  9. Cow Uncle 学习了叉积的一点运用,叉积真的不错

    Cow Uncle Time Limit: 4000/2000MS (Java/Others) Memory Limit: 128000/64000KB (Java/Others) SubmitSta ...

  10. LDA工程实践之算法篇之(一)算法实现正确性验证(转)

    研究生二年级实习(2010年5月)开始,一直跟着王益(yiwang)和靳志辉(rickjin)学习LDA,包括对算法的理解.并行化和应用等等.毕业后进入了腾讯公司,也一直在从事相关工作,后边还在yiw ...