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,但是缺点也很明显,每次都会把所有数据拷贝一份,牺牲了性能获取了线程安全,到底怎样取舍需要根据具体情况来确定。
- Java修改ArrayList的常见异常
- Java 常见的异常
- Java常见的异常
- 常见的Java异常
- java常见的异常
- Java的常见异常
- java常见的异常
- 常见的Java异常
- java常见的异常
- java常见的异常
- JAVA 常见的异常
- Java常见的异常
- java语言基础(66)——集合框架(arrayList ConcurrentModificationException 并发修改异常的解决方案)
- ArrayList 的java.util.ConcurrentModificationException异常?
- Java中常见的异常
- Java中常见的异常
- Java中常见的异常
- Java中常见的异常
- VBA:针对一个工作簿中多个表,显示表名中含特定字符的表,其他表隐藏
- 如何设置淘宝镜像
- 查看Chome浏览器中已保存的密码
- 文章标题
- VS自动代码整理和自动导包
- Java修改ArrayList的常见异常
- LRM-00109: could not open parameter file '/oracle/app/oracle/11.2.0/db_1/dbs/initracdb2.ora
- 队列实现
- Servlet的转发 VS 从定向
- ETL: 大数据之技术核心
- 酷我开发中遇到的问题
- 第13周OJ实践 学生成绩的处理
- elasticsearch5.0启动出现的错误
- Oracle-Alert log解读