关于java:foreach循环你真的了解它么

你走不出劳碌的圈子,永远不晓得里面的世界有多精彩。

前言

置信大家必定都看过阿里巴巴开发手册,而在阿里巴巴开发手册中明确的指出,不要再foreach循环外面进行元素的add和remove,如果你非要进行remove元素,那么请应用Iterator形式,如果存在并发,那么你肯定要抉择加锁。

foreach

    public static void main(String[] args) {
        List<String> list = new ArrayList<String>();
        list.add("a");
        list.add("b");
        list.add("c");
        list.add("d");
        list.add("e");

        for (String s : list) {
            if ("b".equalsIgnoreCase(s) || "c".equalsIgnoreCase(s)) {
                list.remove(s);
            }
        }
        System.out.println(JSONObject.toJSONString(list));
    }

输入后果:

Exception in thread "main" java.util.ConcurrentModificationException
    at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:909)
    at java.util.ArrayList$Itr.next(ArrayList.java:859)
    at list.Test03.main(Test03.java:23)

Process finished with exit code 1

异样起因

咱们从异样信息动手,at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:909)

final void checkForComodification() {
    if (modCount != expectedModCount)
        throw new ConcurrentModificationException();
}

异样信息中的909行也就是从这里开始的,大家必定会想他在比拟两个值 modCountexpectedModCount,那么这两个变量是什么呢?

其实说白了,他们就是来示意批改次数的变量,其中modCount示意汇合的批改次数,这其中包含了调用汇合自身的add办法等批改办法时进行的批改和调用汇合迭代器的批改办法进行的批改。而expectedModCount则是示意迭代器对汇合进行批改的次数。

expectedModCount 是个什么鬼?从ArrayList 源码可知,这个变量是一个局部变量,也就是说每个办法外部都有expectedModCountmodCount 的判断机制,进一步来讲,这个变量就是 预期的批改次数,而这个判断机制,很多人都会间接通知你说fail-fas机制,你说这个机制,然而很多人都晓得,你想解释明确他,须要点功夫的,了解起来必定更须要花点工夫的。

先来看看反编译之后的代码,如下:

    public static void main(String[] args) {
        List<String> list = new ArrayList();
        list.add("a");
        list.add("b");
        list.add("c");
        list.add("d");
        list.add("e");
        Iterator var2 = list.iterator();

        while(true) {
            String s;
            do {
                if (!var2.hasNext()) {
                    System.out.println(JSONObject.toJSONString(list));
                    return;
                }

                s = (String)var2.next();
            } while(!"b".equalsIgnoreCase(s) && !"c".equalsIgnoreCase(s));

            list.remove(s);
        }
    }

看外面应用的也是迭代器,也就是说,其实 foreach 每次循环都调用了一次iteratornext()办法,这也是为什么咱们从堆栈信息看到的起因。

foreach形式中调用的remove办法,是ArrayList外部的remove办法,会更新modCount属性

到此,你可能还想看看,ArrayList类中的remove办法

    public E remove(int index) {
        rangeCheck(index);

        modCount++;
        E oldValue = elementData(index);

        int numMoved = size - index - 1;
        if (numMoved > 0)
            System.arraycopy(elementData, index+1, elementData, index,
                             numMoved);
        elementData[--size] = null; // clear to let GC do its work

        return oldValue;
    }

看到此办法中,有一个modCount++的操作,也就是说,modCount会始终更新变动。

而这时候咱们就能够这么了解了,咱们第一次迭代的时候 AA != DD ,间接迭代第二次,这时候就相等了,执行remove()办法,这时候就是modCount++,再次调用next()的时候,modCount = expectedModCount 这个就不成立了,所以异样信息呈现了,其实也能够了解为在 hasNext() 外面,cursor != size 而这时候就会呈现谬误了。

也就是说 remove办法它只批改了modCount,并没有对expectedModCount做任何操作。

能够看出,迭代器调用的是 Iterator 类的 remove 办法,foreach调用的是 ArrayList类 的remove办法,这是惟一的区别

迭代器

为什么阿里巴巴的标准手册会这样子定义?

它为什么举荐咱们应用 Iterator呢?

间接应用迭代器会批改expectedModCount,而咱们应用foreach的时候,remove办法它只批改了modCount,并没有对expectedModCount做任何操作,而Iterator就不会这个样子。

    public static void main(String[] args) {
        List<String> list = new ArrayList<String>();
        list.add("a");
        list.add("b");
        list.add("c");
        list.add("d");
        list.add("e");

        Iterator<String> iterator = list.iterator();
        while (iterator.hasNext()) {
            String item = iterator.next();
            if ("b".equalsIgnoreCase(item) || "c".equalsIgnoreCase(item)) {
                iterator.remove();
            }
        }
        System.out.println("iterator:" + JSONObject.toJSONString(list));
    }

输入后果:

iterator:["a","d","e"]

Process finished with exit code 0

能够看出后果是正确的,上面咱们来剖析一下:

先来看看反编译之后的代码:

    public static void main(String[] args) {
        List<String> list = new ArrayList();
        list.add("a");
        list.add("b");
        list.add("c");
        list.add("d");
        list.add("e");
        Iterator iterator = list.iterator();

        while(true) {
            String item;
            do {
                if (!iterator.hasNext()) {
                    System.out.println("iterator:" + JSONObject.toJSONString(list));
                    return;
                }

                item = (String)iterator.next();
            } while(!"b".equalsIgnoreCase(item) && !"c".equalsIgnoreCase(item));

            iterator.remove();
        }
    }

次要察看remove()办法的实现,那么须要先看 ArrayList.class:

    public Iterator<E> iterator() {
        return new Itr();
    }

    /**
     * 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;

        Itr() {}

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

        public void remove() {
            if (lastRet < 0)
                throw new IllegalStateException();
            checkForComodification();     //第一步

            try {
                ArrayList.this.remove(lastRet);   //第二步:调用list的remove办法
                cursor = lastRet;
                lastRet = -1;
                expectedModCount = modCount;         //第三步:modCount是remove办法去保护更新,
                                                    //因为第一步中校验 modCount 和 expectedModCount 是否相当等
            } catch (IndexOutOfBoundsException ex) {
                throw new ConcurrentModificationException();
            }
        }

        final void checkForComodification() {
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
        }
    }

list.iterator() 返回的是一个 Itr 对象(ArrayList公有的实例外部类),执行 iterator.remove() 办法时:

第一步:调用 checkForComodification() 办法,作用:判断modCountexpectedModCount 是否相当;

第二步:也就是foreach形式中调用的remove办法,在ArrayList外部的remove办法,会更新modCount属性;

第三步:将更新后的modCount从新赋值给expectedModCount变量,相当于有一个更新操作,才通过了上边第一步的校验!

Java8的新个性

    public static void main(String[] args) {
        List<String> list = new ArrayList<String>();
        list.add("a");
        list.add("b");
        list.add("c");
        list.add("d");
        list.add("e");

        list.removeIf(item -> "b".equalsIgnoreCase(item) || "c".equalsIgnoreCase(item));
        System.out.println("iterator:" + JSONObject.toJSONString(list));
    }

总结

for-each循环不仅实用于遍历汇合和数组,而且能让你遍历任何实现Iterator接口的对象;最最要害的是它还没有性能损失。而对数组或汇合进行批改(增加删除操作),就要用迭代器循环。所以循环遍历所有数据的时候,能用它的时候还是抉择它吧。

评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

这个站点使用 Akismet 来减少垃圾评论。了解你的评论数据如何被处理