Java修改ArrayList的常见异常

来源:互联网 发布:狸窝软件官网 编辑:程序博客网 时间:2024/06/11 03:20

Java修改ArrayList的常见异常

太长懒得看:对ArrayList进行遍历和修改,要么都用Iterator,要么都不用Iterator。如果非要一边用Iterator遍历,一边不用Iterator修改,请用CopyOnWriteArrayList

开篇首先看一段有问题的代码:

/**     * 修改数组(添加或者删除)中的元素,此处以删除数组为例。     *      * @param array     *            要修改的数组     */    private ArrayList<String> modifyArrayError0(ArrayList<String> arrayList) {        if (arrayList != null) {            // 删除数组中为3的元素            for (String value : arrayList) {                if (value.equals("3")) {                    arrayList.remove(value);                }            }        }        System.out.println("success");        return arrayList;    }

上面的代码是有问题的,执行会遇到java.util.ConcurrentModificationException的错误。我想会有不少人看不出到底哪里出错。那么,我们可以先把上述代码等价变换一下:

private ArrayList<String> modifyArrayError1(ArrayList<String> arrayList){        if(arrayList != null){            //删除数组中为3的元素            Iterator<String> iterator = arrayList.iterator();            while(iterator.hasNext()){                String value = iterator.next();                if(value.equals("3")){                    arrayList.remove(value);                }            }        }        return arrayList;    }

modifyArrayError1与modifyArrayError0是等价的,在java的“foreach”语句中,其实就是利用了Collection的Iterator进行自动遍历,不需要手动改变序号i的数值了。但是这样,还是有很多人不明白为啥报异常。
既然我们什么都不了解,那么直接去看源码就是一条思路,“源码面前,了无秘密”。
根据报错信息:at java.util.ArrayList$Itr.next(ArrayList.java:851),找到如下的源码:

public class ArrayList<E>{..........    protected transient int modCount = 0;...../**     * 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;        @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];        }        final void checkForComodification() {            if (modCount != expectedModCount)                throw new ConcurrentModificationException();        }    }

在执行到Iterator.next()时,会调用checkForComodification检查modCount和expectedModCount是否相等,如果不相等的话就抛出了ConcurrentModificationException。
modCount是ArrayList内部记录修改次数的字段,每次对ArrayList的修改都会导致modCount加一,而当调用ArrayList.iterator()返回迭代器的时候,会new一个Itr,此时初始化expectedModCount = modCount。既然抛出异常,就说明两者已经不相等,那么我们就去找修改两者值的地方。
在Iterator类中两者是一直相等的,那么只有到ArrayList去找了。每当调用ArrayList的add或者remove的时候,都会对modCount++,而此时并没有对expectedModCount++,于是两者就不相等了。
由以上的分析,我们分析一下我们抛出错误的代码,代码对ArrayList遍历都是通过Iterator,而删除ArrayList的元素是通过ArrayList的remove方法,所以会使expectedModCount与modCount不相等,抛出异常。
既然通过ArrayList删除会导致两者不相等,那么可以看一下Iterator有没有方法可以删除,正好,Iterator有一个remove可以用。所以,解决方法要么就是用Iterator遍历并通过Iterator删除,要么就是不用Iterator遍历不用Iterator删除。

/**     * 通过Iterator遍历并通过Iterator删除     * @param arrayList 要修改的ArrayList     * @return 修改后的ArrayList     */private ArrayList<String> modifyArrayCorrect0(ArrayList<String> arrayList){        if(arrayList != null){            //删除数组中为3的元素            Iterator<String> iterator = arrayList.iterator();            while(iterator.hasNext()){                String value = iterator.next();                if(value.equals("3")){                    iterator.remove();                }            }        }        return arrayList;    }/**     * 不通过Iterator遍历和修改     * @param arrayList 要修改的ArrayList     * @return 修改后的ArrayList     */    private ArrayList<String> modifyArrayCorrect1(ArrayList<String> arrayList){        if(arrayList != null){            //删除数组中为3的元素            for(int i = 0; i < arrayList.size(); ++i){                if(arrayList.get(i).equals("3")){                    arrayList.remove(i);                    //删除了一个元素,需要改变一下序号                    i--;                }            }        }        return arrayList;    }

但是总有些不撞南墙不回头的人会问:我就想用Iterator遍历用ArrayList删除整么办?幸好,还有CopyOnWriteArrayList来拯救:

/**     * 通过CopyonWriteArrayList解决异常为题     * @param arrayList 要修改的ArrayList     * @return 修改后的ArrayList     */    private CopyOnWriteArrayList<String> modifyArrayCorrect2(CopyOnWriteArrayList<String> arrayList){        if(arrayList != null){                      //删除数组中为3的元素            Iterator<String> iterator = arrayList.iterator();            while(iterator.hasNext()){                String value = iterator.next();                if(value.equals("3")){                    arrayList.remove(value);                }            }        }        return arrayList;    } 

CopyOnWriteArrayList何许人也?本领整么会如此强大?那可说来话长了。。。
既然时间都贵如油,我就长话短说了:名字就能反映其功能,在写操作时复制一份数据出来。CopyOnWriteArrayList在add或者remove时,都会把所有数据拷贝出一份,然后在副本上添加或者删除元素,当完成操作后,就会把用复制品替代“元身”,以后就直接用复制的那份数据了。此过程是线程安全的,不会抛出ConcurrentModificationException。在完成操作前,你通过get获取到的数据还是以前的“元身”,只有当完成操作后去读才能获得最新的。

public class CopyOnWriteArrayList<E>{    private E get(Object[] a, int index) {        return (E) a[index];    }/**     * Appends the specified element to the end of this list.     *     * @param e element to be appended to this list     * @return {@code true} (as specified by {@link Collection#add})     */    public boolean add(E e) {        final ReentrantLock lock = this.lock;        lock.lock();        try {            Object[] elements = getArray();            int len = elements.length;            Object[] newElements = Arrays.copyOf(elements, len + 1);            newElements[len] = e;            setArray(newElements);            return true;        } finally {            lock.unlock();        }    }    public Iterator<E> iterator() {        return new COWIterator<E>(getArray(), 0);    }    static final class COWIterator<E> implements ListIterator<E>     {        public E next() {            if (! hasNext())                throw new NoSuchElementException();            return (E) snapshot[cursor++];        }        /**         * Not supported. Always throws UnsupportedOperationException.         * @throws UnsupportedOperationException always; {@code remove}         *         is not supported by this iterator.         */        public void remove() {            throw new UnsupportedOperationException();        }        /**         * Not supported. Always throws UnsupportedOperationException.         * @throws UnsupportedOperationException always; {@code add}         *         is not supported by this iterator.         */        public void add(E e) {            throw new UnsupportedOperationException();        }    }}

可以看到CopyOnWriteArrayList的add函数中是通过Arrays.copyOf拷贝出一份新的数据,然后将要add的元素e添加到新的数据里,最后用新的数据替换就的数据,整个过程是加锁的。其Iterator就不支持remove和add操作,所以CopyOnWriteArrayList可以避免抛出ConcurrentModificationException,但是缺点也很明显,每次都会把所有数据拷贝一份,牺牲了性能获取了线程安全,到底怎样取舍需要根据具体情况来确定。

0 0