美文网首页
ArrayList中remove方法的坑

ArrayList中remove方法的坑

作者: sunpy | 来源:发表于2018-09-12 11:35 被阅读515次
    秀秀.jpg

    问题

    ArrayList是我们经常在代码中使用的集合类,但是ArrayList在执行remove方法时会出现ConcurrentModificationException。针对这个问题进行代码分析。

    例子

    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.add("f");
        list.add("g");
        list.add("h");
            
        Iterator<String> it = list.iterator();
            
        while (it.hasNext()) {
            String str = it.next();
                
            if (str.equals("f")) {
                list.remove(str);
            }
        }
    }
    

    结果:


    1.png

    说明:在Debug执行过程中发现,ArrayList在执行remove方法移除元素之后,再执行it.next这句话抛出了异常。

    源码分析

    ArrayList的remove方法:

    public E remove(int index) {
          // 先检查下标索引是是否越界
          rangeCheck(index);
          // ArrayList的修改次数加1
          modCount++;
          // 获取索引对应的元素值
          E oldValue = elementData(index);
          // 获取删除元素后,需要移动的元素的个数
          int numMoved = size - index - 1;
          if (numMoved > 0)
              // 将元素进行移动拷贝
              System.arraycopy(elementData, index+1, elementData, index,
                                 numMoved);
          // 最后将多出的位置设置为空,这样说明是没有引用的对象了
          elementData[--size] = null; // Let gc do its work
          // 返回删除的旧值
          return oldValue;
    }
    

    说明:思想是ArrayList的删除方法,将指定位置元素删除,然后将当前位置后面的元素向前拷贝的方式移动。但是注意一点细节,modCount++这步操作,将ArrayList的修改次数加1。而后面遍历时发现是通过使用这个字段来判断,当前的集合类是否被并发修改。

    ArrayList中Iterator迭代器的实现

    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;
    
            public boolean hasNext() {
                return cursor != size;
            }
    
            @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];
            }
    
            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();
                }
            }
    
            final void checkForComodification() {
                if (modCount != expectedModCount)
                    throw new ConcurrentModificationException();
            }
    }
    

    说明:
    ① 在初始化Itr时expectedModCount = modCount = 8 。
    ② 在执行next方法的第一步先进行了checkForComodification方法的检查,因为我们之前进行了remove操作,那么modCount数值减一,实际modCount = 7 。
    ③ modCount 数值和expectedModCount 数值不相等,抛出ConcurrentModificationException异常。

    正确写法

    使用Itr自身提供的remove方法:

    public class MyTest {
    
        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.add("f");
            list.add("g");
            list.add("h");
            
            Iterator<String> it = list.iterator();
            
            while (it.hasNext()) {
                String str = it.next();
                
                if (str.equals("f")) {
                    it.remove();
                }
            }
            
            System.out.println(list);
        }
    }
    

    结果:


    2.png

    说明:使用Itr提供的remove方法,可以发现expectedModCount是随着modCount变化而发生变化的,所以是不需要考虑modCount修改次数不一致的问题。

    思考

    foreach中的remove方法实际上使用list.remove一样会报ConcurrentModificationException异常。因为foreach在jvm中还是会解析成Iterator来执行的,实际上和错误例子是一样的效果。

    相关文章

      网友评论

          本文标题:ArrayList中remove方法的坑

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