1. 异常出现场景
1. 异常场景
异常发现于我的如下一段代码中:
List<String> arrList = new ArrayList<String>() {{
add("maple");
add("Maple");
add("");
add("boy");
add("Boys");
}};
for (String str:arrList) {
if (str.equals("Boys")) {
arrList.add(str);
}
}
代码中我使用for-each循环arrayList数组,当数组中出现与“Boys”相等时,增加一个“Boys”。运行该段代码后的报错信息如下:
Exception in thread "main" java.util.ConcurrentModificationException
at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:909)
at java.util.ArrayList$Itr.next(ArrayList.java:859)
at mapleboys.DemoMain.main(DemoMain.java:17)
2. 异常出现原理
通过查看for-each编译后的class文件(如下),可以发现for-each被编译器视作iterator。
List<String> arrList = new ArrayList<String>() {{
this.add("maple");
this.add("Maple");
this.add("");
this.add("boy");
this.add("Boys");
}};
Iterator var2 = arrList.iterator();
while(var2.hasNext()) {
String str = (String)var2.next();
if (str.equals("Boys")) {
arrList.add(str);
}
}
同时,我们注意到报错信息中ArrayList$Itr.next方法调用ArrayList$Itr.checkForComodification时报错了,查看其源代码可以发现:
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();
}
可以看到是modCount和expectedModCount不相等,导致报错。不去深究modCount和expectedModCount的具体含义,也可从arrayList的add方法中找到出现错误的原因。
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
发现modCount增加了,但是expectedModCount没有增加导致值不相等,从而引发异常。(当然如果在遍历中进行删除操作也会引发同样的异常)。
3. 五种解决方案
对于循环中增加或删除元素引发的异常,有以下几种解决方案,其中部分方案不适合进行增加操作。
3.1 for循环
for循环没有使用iterator,因此避免了异常出现
for(int i = 0; i < 5;i++) {
if (arrList.get(i).equals("Boys"))
arrList.add("Boys");
}
3.2 iterator操作
出现异常的原因是采用iterator进行遍历时,但是使用了arrayList的方法进行添加,因此异常,那么使用iterator来操作,就可以避免异常出现,但是iterator没有加入add方法,但是进行删除时可以使用iterator来remove进行异常避免,代码如下:
Iterator it = arrList.iterator();
while(it.hasNext()) {
if (it.next().equals("Boys")) {
it.remove();
}
}
3.3 并发容器
采用并发容器,这样的集合容器在遍历时复制原有集合的内容,在拷贝的集合上进行遍历,删除/增加操作在原集合上进行,这样iterator不会检测到修改,因此可以避免异常。代码如下:
List<String> arrList = new CopyOnWriteArrayList<String>() {{
add("maple");
add("Maple");
add("");
add("boy");
add("Boys");
}};
for (String str:arrList) {
if (str.equals("Boys")) {
arrList.add(str);
}
}
3.4 for-each循环
通过使用arrayList的方法添加/删除后,跳出循环,避免iterator遍历时进行检测。代码如下:
for(String str:arrList) {
if (str.equals("Boys")) {
arrList.add(str);
break;
}
}
3.5 流操作
通过stream的filter方法进行删除操作,代码如下:
arrList = arrList.stream().filter(str -> !str.equals("Boys")).collect(Collectors.toList());