• Foreach删除元素(ArrayList)报错分析


    普通循环:利用index实现

    增强型循环:通过迭代器实现 

    示例代码:

    public class ArrayListTest {
    
        public static void main(String[] args) {
            normalFor(getList());   //普通循环
            iterator(getList());      //增强循环-迭代器
            forEach(getList());     //增强循环-foreach方式
        }
    
        //普通循环
        private static void normalFor(List<String> list) {
            for (int i = 0 ; i < list.size() ; i++){
                if ("b".equalsIgnoreCase(list.get(i)) || "c".equalsIgnoreCase(list.get(i))){
                    list.remove(i);
                }
            }
            System.out.println("normalFor:"+JSONObject.toJSONString(list));
        }
    
      //增强循环-迭代器
      private static void iterator(List<String> list) {
            Iterator iterator = list.iterator();
            while (iterator.hasNext()){
                String str = (String) iterator.next();
                if ("b".equalsIgnoreCase(str) || "c".equalsIgnoreCase(str)){
                    iterator.remove();
                }
            }
            System.out.println("iterator:"+JSONObject.toJSONString(list));
        }
       //增强循环-foreach方式
        private static void forEach(List<String> list) {
            for (String str : list){
                if ("b".equalsIgnoreCase(str) || "c".equalsIgnoreCase(str)){
                    list.remove(str);
                }
            }
            System.out.println("forEach:"+JSONObject.toJSONString(list));
        }
    
        private static List<String> getList(){
            List<String> list = new ArrayList<>();
            list.add("a");
            list.add("b");
            list.add("c");
            list.add("d");
            list.add("e");
            return list;
        }
    }
    

    输出

    normalFor:["a","c","d","e"]
    iterator:["a","d","e"]
    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 com.qxy.collection.ArrayListTest.forEach(ArrayListTest.java:47)
      at com.qxy.collection.ArrayListTest.main(ArrayListTest.java:21)
    
    Process finished with exit code 1
    

      

    从上边可以看出

    类型 输出结果
    普通循环 正常输出,结果错误
    增强循环-迭代器 正常输出,结果正确
    增强循环-foreach 报异常

    普通循环

    普通循环,底层是数组,在remove操作时,被删除元素的后边所有的元素,会往前挪挪一位。咱们还是看图,比较直观

    当第一次删除时,此时的 i = 1,b正常删除,c、d、e此时都往前挪了一位,然后执行了 i+1 变成了2,也就是d 的位置,一直往后都没匹配到c,所以导致c为正常删除。

    增强循环-迭代器

    在分析之前,我们先来看看反编译之后的代代码

    public class ArrayListTest {
        ...
        private static void iterator(List<String> list) {
            Iterator iterator = list.iterator();
            while(true) {
                String str;
                do {
                    if (!iterator.hasNext()) {
                        System.out.println("iterator:" + JSONObject.toJSONString(list));
                        return;
                    }
                    str = (String)iterator.next();
                } while(!"b".equalsIgnoreCase(str) && !"c".equalsIgnoreCase(str));
    
                iterator.remove();//不同的地方:调用迭代器的remove方法
            }
        }
        private static void forEach(List<String> list) {
            Iterator var1 = list.iterator();
            while(true) {
                String str;
                do {
                    if (!var1.hasNext()) {
                        System.out.println("forEach:" + JSONObject.toJSONString(list));
                        return;
                    }
                    str = (String)var1.next();
                } while(!"b".equalsIgnoreCase(str) && !"c".equalsIgnoreCase(str));
    
                list.remove(str);//不同的地方:调用list的remove方法
            }
        }  
      ...
    } 

     从上边的代码来看,迭代器 和 foreach 的方法很类似,唯一的区别就是 remove() 方法

    迭代器调用的是  Iterator 类的 remove 方法

    foreach调用的是 ArrayList类 的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 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() 方法,此方法作用:modCount 和 expectedModCount 是否相当

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

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

    到此,你可能还想看看,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会一直更新变化。

    总结:jdk源码离我们那么近,但是,总是这样完美的擦肩而过,以后要多啃啃,不要错过!!!

    Read the fucking manual and source code
  • 相关阅读:
    mysql 约束条件介绍
    mysql 约束条件目录
    mysql 日期类型
    mysql float 浮点型
    mysql int 整数类型 解释显示宽度 和 存储宽度
    mysql 整数类型 数值类型 tinyint
    【洛谷P4096】Eden 的博弈树
    【GMOJ6824】英雄联盟
    【GMOJ6826】隔膜
    【POJ 2420】A Star not a Tree?
  • 原文地址:https://www.cnblogs.com/qxynotebook/p/11253257.html
Copyright © 2020-2023  润新知