如何操作你的ArrayList

错误的写法

  1. 不会抛异常,但是会漏掉数据
List<Integer> list = new ArrayList<>();
list.add()...
for(int i = 0;i < list.size();i++) {
            if(i == xxx) {
                list.remove(i);
            }
        }

该种写法每一次随着条件的成立,会带来如下问题

  • 每一次remove掉一个元素,导致数组后面所有的所有元素向前移位,之后索引本身自增,结果就是reomve后面的那个元素会被漏掉检查
  1. 快速抛异常
List<Integer> list = new ArrayList<>();
list.add()...
while (iterator.hasNext()) {
    list.remove(iterator.next());
}

该种方法会快速的抛出异常,相同的写法如下:

List<Integer> list = new ArrayList<>();
list.add()...
for(Integer integer : list) {
    list.remove(integer);
}

之所以写法相同是因为forEach循环经过JVM解析之后的指令同Iterator一样,可见第二种写法仅仅是Java提供的一种语法糖。快速抛出异常的原因参见ArrayList源码,部分源码如下:

    public boolean remove(Object o) {
        if (o == null) {
            for (int index = 0; index < size; index++)
                if (elementData[index] == null) {
                    fastRemove(index);
                    return true;
                }
        } else {
            for (int index = 0; index < size; index++)
                if (o.equals(elementData[index])) {
                    fastRemove(index);
                    return true;
                }
        }
        return false;
    }

    private void fastRemove(int index) {
        modCount++;
        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
    }

每次remove()操作都会修改modCount的值,但是每一调用iterator.next()方法会检查exceptedModCount==modCount, 如果不相等就会抛出ConcurrentModificationException, 部分源码如下:

    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 void checkForComodification() {
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
    }

正确的写法

  1. 巧妙的写法
        for(int i = list.size()-1;i >= 0;i--) {
            list.remove(i);
        }

从后向前遍历,每次remove操作之后,后面的元素仍然向前移动,但是前面元素的顺序不便,因此索引自增之后指向的仍然原来应该指向的元素。

  1. 官方推荐的写法
       while (iterator.hasNext()) {
           iterator.next();
           iterator.remove();
       }

iterator.remove()方法不同于ArrayList.remove()方法,源码如下:

        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();
            }
        }

每次remove()操作都会将modCount同步到exceptedModCount值中,这样就可以安全的删除数组中的元素。

官方的话

 * The iterators returned by this class's {@link #iterator() iterator} and
 * {@link #listIterator(int) listIterator} methods are <em>fail-fast</em>:</a>
 * if the list is structurally modified at any time after the iterator is
 * created, in any way except through the iterator's own
 * {@link ListIterator#remove() remove} or
 * {@link ListIterator#add(Object) add} methods, the iterator will throw a
 * {@link ConcurrentModificationException}.  Thus, in the face of
 * concurrent modification, the iterator fails quickly and cleanly, rather
 * than risking arbitrary, non-deterministic behavior at an undetermined
 * time in the future.

推荐阅读更多精彩内容