ArrayList循环遍历并删除元素的几种情况

如下代码,想要循环删除列表中的元素b,该怎么处理?

public class ListDemo {
    public static void main(String[] args) {
        ArrayList<String> arrayList = new ArrayList<String>();
        arrayList.add("a");
        arrayList.add("b");
        arrayList.add("b");
        arrayList.add("c");
        arrayList.add("d");
        arrayList.add("e");
        remove(arrayList);
//        remove2(arrayList);
//        remove3(arrayList);
        System.out.println(arrayList);
    }
}

方法一:for循环遍历

       public static void remove(ArrayList<String> list) {
            for (int i = 0; i < list.size(); i++) {
                String s = list.get(i);
                if (s.equals("b")) {
                    list.remove(s);
                }
            }
        }

输出结果:

[a, b, c, d, e]

由结果可知,第二个元素b并未删除,原因是当第一个元素b被删除后,它后面所有的元素都向前移动了一个单位,循环时导致第二个元素b漏掉了(本例中从下标2变为了下标1,而下标1已经遍历过了),可以通过源码来看:

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

进入 fastRemove方法:

    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 }

方法二:foreach循环

        public static void remove2(ArrayList<String> list) {
            for (String s : list) {
                if (s.equals("b")) {
                    list.remove(s);
                }
                System.out.println(s);
            }
        }

会报错:java.util.ConcurrentModificationException。这是因为在这里,foreach循环遍历容器本质上是使用迭代器进行遍历的,会对修改次数modCount进行检查,不允许集合进行更改操作,源码如下:

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

方法三:使用迭代器遍历删除

        public static void remove3(ArrayList<String> list) {
            Iterator<String> it = list.iterator();  
            while (it.hasNext()) {  
                String s = it.next();  
                if (s.equals("b")) {  
                    it.remove();  
                }  
            }  
        }

使用迭代器遍历删除时,能够避免方法二中出现的问题。这是因为:在ArrayList中,modCount是指集合的修改次数,当进行add或者delete时,modCount会+1;expectedModCount是指集合的迭代器的版本号,初始值是modCount,但是当集合进行add或者delete操作时,modCount会+1,而expectedModCount不会改变,所以方法二中会抛出异常。但是it.remove操作时,会同步expectedModCount的值,把modCount的值赋予expectedModCount。所以不会抛出异常。it.remove的源码如下:

        public void remove() {
            if (lastRet < 0)
                throw new IllegalStateException();
            checkForComodification();

            try {
                ArrayList.this.remove(lastRet);  // 移除元素
                cursor = lastRet;
                lastRet = -1;
                expectedModCount = modCount;    // 同步expectedModCount的值
            } catch (IndexOutOfBoundsException ex) {
                throw new ConcurrentModificationException();
            }
        }

总结:如果想正确的循环遍历删除元素,需要使用方法三,也就是迭代器遍历删除的方法。

posted @ 2019-02-20 15:00  51life  阅读(...)  评论(... 编辑 收藏