Java修改ArrayList的常见异常,javaarraylist
Java修改ArrayList的常见异常,javaarraylist
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,但是缺点也很明显,每次都会把所有数据拷贝一份,牺牲了性能获取了线程安全,到底怎样取舍需要根据具体情况来确定。
后记:后来在看安卓面试常见问题的时候,发现此问题是有专业术语的:
1、fail-fast:在遍历一个集合时,当集合结构被修改,会抛出Concurrent Modification Exception
2、fail-safe:任何对集合结构的修改都会在一个复制的集合上进行修改,因此不会抛出ConcurrentModificationException
相关文章
- 暂无相关文章
用户点评