ArrayList循环遍历删除元素出现问题
- 1. 第一种循环删除出现异常
- 2. 第二种循环删除不报异常,但是会出现有些数据没有删除的情况
- 3. 总结
1. 第一种循环删除出现异常
import java.util.ArrayList;
/**
* @author LanceQ
* @version 1.0
* @time 2021/4/28 19:16
*/
public class ListTest {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
list.add("1");
list.add("2");
list.add("3");
for(String l:list){
System.out.println(list.remove(l));//并发修改异常
}
}
}
循环删除第二个开始爆并发异常
true
Exception in thread "main" java.util.ConcurrentModificationException
- 先了解一下remove方法,其方法有两个,一个是下面这个通过对象删除,一个是通过索引删除。
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;
}
- 一般情况下程序的执行路径会走到else路径下,最终调用faseRemove方法:
private void fastRemove(int index) {
modCount++; //AbstractList中的变量
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写法是对实际的Iterable、hasNext、next方法的简写,问题在上文的fastRemove方法中,可以看到第一行把modCount变量的值加一,但在ArrayList返回的迭代器(该代码在其父类AbstractList中):
protected transient int modCount = 0;
public E next() {
checkForComodification();<--------这里
try {
int i = cursor;
E next = get(i);
lastRet = i;
cursor = i + 1;
return next;
} catch (IndexOutOfBoundsException e) {
checkForComodification();
throw new NoSuchElementException();
}
}
final void checkForComodification() {
if (modCount != expectedModCount) <----判断
throw new ConcurrentModificationException();<--------这里异常
}
int expectedModCount = modCount;
这里会做迭代器内部修改次数检查,因为上面的remove(Object)方法修改了modCount的值,所以才会报出并发修改异常。要避免这种情况的出现则在使用迭代器迭代时(显示或for-each的隐式)不要使用ArrayList的remove,改为用Iterator的remove即可。
import java.util.ArrayList;
import java.util.Iterator;
/**
* @author LanceQ
* @version 1.0
* @time 2021/4/28 19:16
*/
public class ListTest {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
list.add("1");
list.add("2");
list.add("3");
//错误的删除方法
// for(String l:list){
// System.out.println(list.remove(l));//并发修改异常
// }
System.out.println(list);
Iterator<String> it = list.iterator();
//这里判断的是下一个元素是否存在
while (it.hasNext()){
System.out.println(it.hasNext());
//需要进行next操作才可以进行remove,
// 否则会出现Java.lang.IllegalStateException异常(非法状态异常)
it.next();
it.remove();
}
System.out.println(list);
}
}
运行结果
[1, 2, 3]
true
true
true
[]
- Java.lang.IllegalStateException异常(非法状态异常)出现的原因是 删除了一个不满足条件的元素。通过Iterator来删除,首先需要使用next方法迭代出集合中的元素,然后才能调用remove方法,否则集合可能抛出java.lang.IllegalStateException异常。
- 注意remove对象是否存在,如果这个记录已被remove掉,再次remove会出现此异常,容易出现在对同一对象(如List)做多次迭代remove的情景中。
2. 第二种循环删除不报异常,但是会出现有些数据没有删除的情况
import java.util.ArrayList;
import java.util.Iterator;
/**
* @author LanceQ
* @version 1.0
* @time 2021/4/28 19:16
*/
public class ListTest {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
list.add("1");
list.add("2");
list.add("3");
list.add("4");
list.add("5");
remove2(list);
}
private static void remove2(ArrayList<String> list) {
System.out.println(list);
for (int i = 0; i < list.size(); i++) {
System.out.println(list.remove(i));
}
System.out.println(list);
}
}
运行结果
[1, 2, 3, 4, 5]
1
3
5
[2, 4]
这次的remove调用的是另一个remove方法
public E remove(int index) {
rangeCheck(index);
modCount++;
E oldValue = elementData(index);
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
return oldValue;
}
- 可以看到会执行System.arraycopy方法,导致删除元素时涉及到数组元素的移动。
- 针对上方出现的运行结果与预想不一致,是由于在遍历第一个字符串时,因为符合删除条件,所以将该元素从数组中删除,并且将后一个元素移动(也就是第二个字符串b)至当前位置,导致下一次循环遍历时,后一个字符串并没有遍历到,所以无法删除。
针对这种情况可以倒序删除的方式来避免:
private static void remove2(ArrayList<String> list) {
System.out.println(list);
for (int i = list.size() - 1; i >= 0; i--) {
System.out.println(list.remove(i));
}
System.out.println(list);
}
[1, 2, 3, 4, 5]
5
4
3
2
1
[]
3. 总结
- 通过foreach方式进行删除的modCount变量的改变,会出现非法状态异常,可通过iterator迭代器的方式进行判断,删除。
- 通过for循环变量list的长度,正序来进行list中元素的移除,会出现漏删除的情况,可通过倒序删除的方式来解决。