美文网首页java 进阶
foreach深入理解--源码解析

foreach深入理解--源码解析

作者: _zhaoyan_ | 来源:发表于2019-07-18 18:42 被阅读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、总结

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

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

    相关文章

      网友评论

        本文标题:foreach深入理解--源码解析

        本文链接:https://www.haomeiwen.com/subject/yyeclctx.html