目录
- 前言
- 正文
- 为什么结果如此不同?
- remove/add方法?
- 如何解决?
前言
在阿里巴巴Java开发手册中,有下面这样的规定:
这篇文章我们就来深入探讨其中的原因。
正文
为什么结果如此不同?
我们先来看看前言中的反例会出现什么意料之外的结果:
----------------------------------------------------------------------------------------------------------------------------------------------------------------------
仅仅是remove的元素不同,为什么会出现如此不同的结果呢?我们反编译上面报错的字节码文件可得:
import java.io.PrintStream;
import java.util.*;
public class Test
{
public Test()
{
}
public static void main(String args[])
{
ArrayList arraylist = new ArrayList();
arraylist.add("1");
arraylist.add("2");
Iterator iterator = arraylist.iterator();
do
{
if(!iterator.hasNext())
break; // 1
String s = (String)iterator.next(); // 2
if("2".equals(s))
arraylist.remove(s);
} while(true);
System.out.println((new StringBuilder()).append("list:").append(arraylist).toString());
}
}
通过这个反编译结果我们可以看到foreach底层其实还是使用iterator进行迭代。并且Debug上面的代码,发现当删除"2"元素后,代码执行到2处时报错;但当删除"1"元素后,代码会执行1处代码退出循环,由于没有执行2处的代码,所以删除"1元素"时不会报错。那么有人可能就会问了:为什么删除"2"元素后,1处代码不执行?我们可以通过查看ArrayList的hasNext()的源码找到答案:
class ArrayList {
private int size; // The size of the ArrayList (the number of elements it contains).
private class Itr implements Iterator {
int cursor; // index of next element to return
public boolean hasNext() {
return cursor != size;
}
}
}
当删除"1"元素后,cursor值为1("2"元素的下标),size值也为1,两者相等,故hasNext()返回false,所以执行1处代码;但当删除"2"元素后,cursor值为0("1"元素的下标),size值还是为1,两者不相等,故hasNext()返回true,所以无法执行1处代码。故而就出现了上面截然不同的结果。
remove/add方法?
那么为什么在执行2处代码时会报错呢?通过上面的报错信息我们可以看出,ConcurrentModificationException是在执行checkForComodification()的过程抛出的,checkForComodification()的源码如下所示:
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
上面方法中的modCount是ArrayList中的一个成员变量,它表示该集合实际被修改的次数;expectedModCount是ArrayList中的一个内部类Itr(Itr是一个Iterator的实现:使用ArrayList.iterator()获取到的迭代器就是Itr类的实例)中的成员变量,它表示这个迭代器期望该集合被修改的次数,需要注意的是这个值是在集合调用iterator()时初始化,并且只有通过该迭代器对集合进行操作时,该值才会发生改变。
那么remove()/add()为什么会导致这两者的值不等呢?它对集合中的元素是怎样进行操作的呢?查看remove方法的源码:
public E remove(int index) {
rangeCheck(index);
checkForComodification();
E result = parent.remove(parentOffset + index);
this.modCount = parent.modCount;
this.size--;
return result;
}
由上面的源代码我们可以看到remove()仅对modCount变量进行了操作。于是我们就可以知道:在foreach(即iterator)对集合进行遍历时,元素在"自己"不知不觉的情况下被删除/添加,这时就会抛出异常,提示用户可能发生了并发修改。
如何解决?
明白了为什么之后,我们就需要思考如何去解决它:
- 使用Iterator提供的remove()。
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
public class Test {
public static void main(String[] args) {
List<String> list = new ArrayList<String>();
list.add("1");
list.add("2");
Iterator iterator = list.iterator();
while(iterator.hasNext()) {
if("2".equals(iterator.next())) {
iterator.remove();
}
}
System.out.println("list:" + list);
}
}
- 如果是List集合,还可以使用listIterator提供的remove()和add()。
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
public class Test {
public static void main(String[] args) {
List<String> list = new ArrayList<String>();
list.add("1");
list.add("2");
Iterator iterator = list.listIterator();
while(iterator.hasNext()) {
if("2".equals(iterator.next())) {
iterator.remove();
// ((ListIterator) iterator).add("3");
}
}
System.out.println("list:" + list);
}
}
- 使用Java8提供的filter进行过滤:在Java8中可以把集合转换成流,并且对于流有一种filter操作,它可以对原始Stream进行某项过滤,通过过滤的元素被留下来生成一个新Stream。
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
public class Test {
public static void main(String[] args) {
List<String> list = new ArrayList<String>() {{
add("1");
add("2");
}};
list = list.stream().filter(name -> ! name.equals("2")).collect(Collectors.toList());
System.out.println("list:" + list);
}
}
原文地址:https://www.cnblogs.com/syhyfh/p/12551092.html