为什么不能在增强for中对集合进行remove/add操作并且如何解决
不知道大家有没有遇到过这样的问题,那就是在增强for循环中对集合中的元素进行remove/add 操作时会发生异常,这是为什么呢?
错误演示
大家可以看一下下面这段代码
List<String> list = new ArrayList<String>() {
{
add("test1");
add("test2");
add("test3");
add("test4");
}};
for (String s : list) {
if (s.equals("test2")) {
list.remove(s);
}
}
System.out.println(list);
执行了这段代码之后,控制台会报错,如下:
报错原因
为什么会报错呢?我们可以看一下生成的.class字节码文件
List<String> list = new ArrayList<String>() {
{
this.add("test1");
this.add("test2");
this.add("test3");
this.add("test4");
}
};
Iterator var2 = list.iterator();
while(var2.hasNext()) {
String s = (String)var2.next();
if (s.equals("test2")) {
list.remove(s);
}
}
System.out.println(list);
我们可以看到,在增强for循环底层其实是用了一个迭代器(Iterator),把这段代码执行同样会有异常
从报错信息提示是在迭代器调用next()
方法的时候用了checkForComodification()
方法,而异常就是这个方法抛出的。
我们来看一下checkForComodification()
的源码:
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
这段代码很简单,执行modCount != expectedModCount
时就会抛出ConcurrentModificationException
异常
首先我们要知道这两个变量是什么?
- modCount: ArrayList的一个成员变量。它表示该集合实际被修改的次数
- expectedModCount: ArrayList的一个内部类——Itr的一个成员变量,表示这个迭代器期望该集合修改的次数。其值是在ArrayList.iterator方法调用时初始化的, 只有通过迭代器对集合进行操作,该值才会改变
那么是什么原因导致这两个变量不相等呢?
接下来我们就看看源码上的remove()
方法(因为remove()
)调用的是fastRemove()
,所以我们直接看fastRemove()
的源码)
private void fastRemove(int index) {
modCount++;
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
}
可以看到,它只修改了modCount,并没有修改expectedModCount
之所以抛出ConcurrentModificationException异常,是因为我们代码中使用了增强for循环,而增强for循环遍历集合是通过迭代器进行的,但是元素的增删操作却直接使用了集合类自己的方法,就导致迭代器遍历元素的时候会发现有一个元素在自己不知不觉的情况下就被删除或者添加了,所以会抛出这个异常来提醒用户,这个类可能发生了并发修改
解决方法
普通for循环
List<String> list = new ArrayList<String>(){
{
add("test1");
add("test2");
add("test3");
add("test4");
}};
for (int i = 0; i < list.size(); i++) {
if (list.get(i).equals("test2")) {
list.remove(i);
}
}
System.out.println(list);
普通for循环删除元素其实存在一个问题,那就是remove操作会改变List中元素的下标,可能存在漏删的情况
直接使用Iterator
List<String> list = new ArrayList<String>(){
{
add("test1");
add("test2");
add("test3");
add("test4");
}};
Iterator iterator = list.iterator();
while (iterator.hasNext()) {
if (iterator.next().equals("test2")) {
iterator.remove();
}
}
System.out.println(list);
使用stream()
List<String> list = new ArrayList<String>(){
{
add("test1");
add("test2");
add("test3");
add("test4");
}};
list = list.stream().filter(s -> !s.equals("test2")).collect(Collectors.toList());
System.out.println(list);
增强for
其实增强for也可以,但是前提是集合中只有一个元素即将被删除,只要删除元素后,立即结束循环体,不在继续遍历就行了,也就是说不执行代码的下一次next()
方法
List<String> list = new ArrayList<String>(){
{
add("test1");
add("test2");
add("test3");
add("test4");
}};
for (String s : list) {
if (s.equals("test2")) {
list.remove(s);
break;
}
}
System.out.println(list);
Collection.removeIf()
List<String> list = new ArrayList<String>(){
{
add("test1");
add("test2");
add("test3");
add("test4");
}};
list.removeIf(s -> s.equals("test2"));
System.out.println(list);
使用CopyOnWriteArrayList
CopyOnWriteArrayList
是JUC并发包里面的一个集合类,用它来替换ArrayList就不会发生异常了
List<String> list = new CopyOnWriteArrayList<String>() {
{
add("test1");
add("test2");
add("test3");
add("test4");
}};
for (String s : list) {
if (s.equals("test2")) {
list.remove(s);
}
}
System.out.println(list);