基础篇--foreach深入理解-源码解析

4 篇文章 0 订阅
1 篇文章 0 订阅

1、概述:

foreach是用来循环遍历的方式之一,在java8中新增加的for循环的简化版,虽然说是简化版,并不是说比for或者iterator好用;
主要区别在于:
(1)fori是通过下标访问;
(2)foreach是通过容器的itrator的next()方法来迭代;
这篇文章主要来介绍foreach。

2、foreach样例展示

举例代码:


/**
 * @ClassName TestForeach
 * @Description TODO
 * @Author zhaoyan
 * @Date 2019/7/17 16:40
 * @Version 1.0
 **/

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

public class TestForeach {

    List<String> list = new ArrayList<String>();

    public TestForeach() {
        this.list.add("北京");
        this.list.add("上海");
        this.list.add("广州");
        this.list.add("深圳");
    }

    public static void main(String[] args) {
//        ArraySplit();
//        IteratorSplit();
        new TestForeach().foreachSplit();
    }


    public void foreachSplit() {

        for (String s : list) {
            System.out.println(s);
        }
    }


}

运行结果如下:

北京
上海
广州

上述代码定义ArrayList集合,循环遍历输出。
执行代码,生成class文件,并进行反编译得到的代码(我是用IDEA,在IDEA的OUT目录下找到class文件,直接点开即可):
22.png
代码如下:

        List<String> list = new ArrayList();
        list.add("北京");
        list.add("上海");
        list.add("广州");
        Iterator iterator = list.iterator();

        while(iterator.hasNext()) {
            String s = (String)iterator.next();
            System.out.println(s);
        }

反编译可以知道,foreach底层是利用迭代器实现的,初始化获得迭代器,判断条件hasNext(),然后获得next(),最后打印这个结果。
另外foreach不支持在循环中添加删除操作,因为在使用foreach循环的时候数组(集合)就已经被锁定不能被修改,否则会报出java.util.ConcurrentModificationException异常;
但是数组的操作中Iterator是可以进行数组的操作的,这点怎么解释呢?
下面来说明一下这点!!!

3、foreach的remove

上车,飞一波,粘贴代码!!!

    public static void foreachSplit() {
        List<String> list = new ArrayList<String>();
        list.add("北京");
        list.add("上海");
        list.add("广州");
        list.add("深圳");
        for (String s : list) {
            System.out.println(s);
            if ("北京".equals(s)) {
                list.remove(s);
            }
        }
    }

运行结果如下:

北京
Exception in thread "main" java.util.ConcurrentModificationException
    at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:901)
    at java.util.ArrayList$Itr.next(ArrayList.java:851)
    at com.zhaoyan.foreach.TestForeach.foreachSplit(TestForeach.java:47)
    at com.zhaoyan.foreach.TestForeach.main(TestForeach.java:19)

说明在迭代器使用过程中,禁止修改迭代器中的内容。否侧会抛出java.util.ConcurrentModificationException。

iterator创建的时候modCount被赋值给了expectedModCount,但是调用list的add和remove方法的时候不会同时自动增减expectedModCount,这样就导致两个count不相等,从而抛出异常。

4、重头戏,源码解析

反编译可以知道,foreach底层是利用迭代器实现的,因此:

Iterator var1 = list.iterator();
package java.util
    /**
     * Returns an iterator over the elements in this list in proper sequence.
     *
     * <p>The returned iterator is <a href="#fail-fast"><i>fail-fast</i></a>.
     *
     * @return an iterator over the elements in this list in proper sequence
     */
    public Iterator<E> iterator() {
        return new Itr();
    }

iterator 这个方法在ArrayList和AbstractList中都存在,但AbstractList是抽象类,是被继承之后,要拥有具体的实现方法,抽象类的方法是一定要进行实现的,具体区分关注我之前写的抽象类的博客:https://blog.csdn.net/ITzhaoyan/article/details/94599218

/**
 * An optimized version of AbstractList.Itr
 */
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
    int expectedModCount = modCount;

cursor:要返回的下一个元素的索引,下一个遍历到的元素的下标,目前还没有开始遍历,所以cursor是0
lastRet :上一次操作的元素的下标,初始值为-1。
expectedModCount :表示迭代器对集合进行修改的次数,是实际上应该轮询的次数;
modCount:modCount是ArrayList的父类AbstractList的一个字段,这个字段的含义是list结构发生变更的次数,通常是add或remove等导致元素数量变更的会触发modCount++。

iterator.hasNext()
        public boolean hasNext() {
            return cursor != size;
        }

size:是集合的大小;
指针的下一个元素还有的话,逻辑为真,没有元素时,循环条件为假,退出循环。
用while进行循环,hasnext作为退出条件进行轮询集合;然后执行下面代码:

String s = (String)iterator.next();
     @SuppressWarnings("unchecked")
        public E next() {
            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];
        }
        //final 修饰的方法,不能被重写覆盖。
        final void checkForComodification() {
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
        }

在调用next()方法的时候,先执行checkForComodification()方法,方法中判断
modCount != expectedModCount,在上述代码中能够肯定,当进行remove操作的时候,
会报java.util.ConcurrentModificationException,就是这段代码的作用。

迭代器内部的每次遍历都会记录List内部的modcount当做预期值,然后在每次循环中用预期值与List的成员变量modCount作比较,但是普通list.remove调用的是List的remove,这时modcount++,但是iterator内记录的预期值=并没有变化,所以会报错。

然后执行获取该下标的数据,并移动元素下标,结果数据进行返回。

5、foreach remove的坑

    public static void foreachSplit() {
        List<String> list = new ArrayList<String>();
        list.add("北京");
        list.add("上海");
        list.add("广州");
        list.add("深圳");
        for (String s : list) {
            System.out.println(s);
            if ("广州".equals(s)) {
                list.remove(s);
            }
        }
    }

结果如下:

北京
上海
广州

Process finished with exit code 0

哎,什么情况,竟然能够删除成功!!!

这就是foreach remove的坑。

删除倒数第二个元素的时候,cursor指向最后一个元素的,而此时删掉了倒数第二个元素后,cursor和list.size()正好相等了,所以hasNext()返回false,遍历结束,这样就成功的删除了倒数第二个元素了。这里面的重点是没有执行next()中的checkForComodification()方法,就直接退出了;

6、正确的操作

6.1、fori删除

下面看一段代码:

    public void foreachRemove() {

        for (int i = 0; i < list.size(); i++) {
            System.out.println(list.get(i));
            list.remove(i);
        }
    }

这段代码能删除,但是结果如下

北京
广州
Process finished with exit code 0

原来的元素1被remove后,后面的向前拷贝,2到了原来1的位置(下标0),3到了原来2的位置(下标1),size由3变2,i+1=1,输出list.get(1)就成了3,2被漏掉了。同理,每执行一次就会漏掉一个元素;

所以用fori进行元素的删除,我们在操作删除之后,要把下标的位置进行前移,这样就能无遗漏的进行元素删除了。

    public void foreachRemove() {

        for (int i = 0; i < list.size(); i++) {
            System.out.println("删除的元素为:--"+list.get(i));
            list.remove(i);
            i--;
        }
    }

执行结果如下:

删除的元素为:—北京
删除的元素为:—上海
删除的元素为:—广州
删除的元素为:—深圳
Process finished with exit code 0

6.2、Iterator迭代器的remove

代码如下:
```
public void iRemove() {

    Iterator<String> itr = list.iterator();

    while (itr.hasNext()) {
        String s = itr.next();
        System.out.println(s);
        itr.remove();
    }
}
执行结果如下:
>北京
上海
广州
深圳
Process finished with exit code 0

迭代器的源码如下:
    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();
        }
    }

```
看源码了解依然有checkForComodification()校验,但是在remove()之后又重新赋值expectedModCount = modCount;,所以校验是通过的。

7、总结

没啥说的了,以上的分享都是单线程的情况,多线程的情况后续分享,

互相学习,感激每个奋战在一线的开发人员,信息化的进步有你们的贡献!!!

简书也有本人脚步···

generated by haroopad 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值