zoukankan      html  css  js  c++  java
  • 【Java】对foreach循环的思考

    阿里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重新设值,感知到了结构的变化
     
    最后,并发操作,需要对迭代器加锁,就不在此赘述了。

  • 相关阅读:
    (转)Python之路,Day6
    (转)函数作用域,匿名函数,函数式编程,面向过程,面向对象
    (转)面向对象编程初步
    day26-多态、封装、反射
    (转)面向对象进阶
    MySql-Mysql技术内幕~SQL编程学习笔记(1)
    Spring MVC-学习笔记(4)数据绑定流程
    Mybatis-学习笔记(10)调用存储过程、存储函数
    Mybatis-学习笔记(9)Mybatis3+spring4+springMVC
    Mybatis-学习笔记(8)常用的注解
  • 原文地址:https://www.cnblogs.com/sqdmydxf/p/7746958.html
Copyright © 2011-2022 走看看