ArrayList中remove方法的坑

2018-09-12  本文已影响515人  sunpy
秀秀.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来执行的,实际上和错误例子是一样的效果。

上一篇下一篇

猜你喜欢

热点阅读