ArrayList中remove方法的坑
问题
ArrayList是我们经常在代码中使用的集合类,但是ArrayList在执行remove方法时会出现ConcurrentModificationException。针对这个问题进行代码分析。
例子
public static void main(String[] args) {
List<String> list = new ArrayList<String>();
list.add("a");
list.add("b");
list.add("c");
list.add("d");
list.add("e");
list.add("f");
list.add("g");
list.add("h");
Iterator<String> it = list.iterator();
while (it.hasNext()) {
String str = it.next();
if (str.equals("f")) {
list.remove(str);
}
}
}
结果:
1.png
说明:在Debug执行过程中发现,ArrayList在执行remove方法移除元素之后,再执行it.next这句话抛出了异常。
源码分析
ArrayList的remove方法:
public E remove(int index) {
// 先检查下标索引是是否越界
rangeCheck(index);
// ArrayList的修改次数加1
modCount++;
// 获取索引对应的元素值
E oldValue = elementData(index);
// 获取删除元素后,需要移动的元素的个数
int numMoved = size - index - 1;
if (numMoved > 0)
// 将元素进行移动拷贝
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
// 最后将多出的位置设置为空,这样说明是没有引用的对象了
elementData[--size] = null; // Let gc do its work
// 返回删除的旧值
return oldValue;
}
说明:思想是ArrayList的删除方法,将指定位置元素删除,然后将当前位置后面的元素向前拷贝的方式移动。但是注意一点细节,modCount++这步操作,将ArrayList的修改次数加1。而后面遍历时发现是通过使用这个字段来判断,当前的集合类是否被并发修改。
ArrayList中Iterator迭代器的实现
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;
public boolean hasNext() {
return cursor != size;
}
@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];
}
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();
}
}
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}
说明:
① 在初始化Itr时expectedModCount = modCount = 8 。
② 在执行next方法的第一步先进行了checkForComodification方法的检查,因为我们之前进行了remove操作,那么modCount数值减一,实际modCount = 7 。
③ modCount 数值和expectedModCount 数值不相等,抛出ConcurrentModificationException异常。
正确写法
使用Itr自身提供的remove方法:
public class MyTest {
public static void main(String[] args) {
List<String> list = new ArrayList<String>();
list.add("a");
list.add("b");
list.add("c");
list.add("d");
list.add("e");
list.add("f");
list.add("g");
list.add("h");
Iterator<String> it = list.iterator();
while (it.hasNext()) {
String str = it.next();
if (str.equals("f")) {
it.remove();
}
}
System.out.println(list);
}
}
结果:
2.png
说明:使用Itr提供的remove方法,可以发现expectedModCount是随着modCount变化而发生变化的,所以是不需要考虑modCount修改次数不一致的问题。
思考
foreach中的remove方法实际上使用list.remove一样会报ConcurrentModificationException异常。因为foreach在jvm中还是会解析成Iterator来执行的,实际上和错误例子是一样的效果。