最近在项目过程中,遇到一个需要在set集合中删除满足条件元素的需求,当时没有多想,直接实用Iterator遍历Set,然后,调用了Set的remove(Object o)方法将对象删除,结果,意外的报了一个异常:java.util.ConcurrentModificationException。经过分析,引起该异常的原因正是因为执行了Set的remove(Object o)方法。下面贴出一段测试代码:
public class Test {
public static void main(String[] args) {
Set<String> set = new HashSet<String>();
set.add("one");
set.add("two");
set.add("three");
Iterator<String> it = set.iterator();
String str = null;
while (it.hasNext()) {
str = it.next();
set.remove(str);
}
System.out.println(set);
}
}
执行结果:
Exception in thread "main" java.util.ConcurrentModificationException at java.util.HashMap$HashIterator.nextEntry(HashMap.java:793)
at java.util.HashMap$KeyIterator.next(HashMap.java:828)
at com.action.ajax.Test.main(Test.java:16)
查看JDK源码发现在迭代Iterator时建立了一个List的内部类Itr。
源码:
public Iterator<E> iterator() {
return new Itr();
}
在new Itr()时有一个关键性的操作
int expectedModCount = modCount;
我们先看一下当List调用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;
}
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;
}
看到这里大家注意一下modCount这个变量,当执行remove方法时此变量执行了modCount++操作。
再看一下 iterator.next()操作
public E next() {
checkForComodification();
try {
E next = get(cursor);
lastRet = cursor++;
return next;
} catch (IndexOutOfBoundsException e) {
checkForComodification();
throw new NoSuchElementException();
}
}
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
看到这儿相信大家已经应该明白了为什么会出现在这个异常了。当迭代器Iterator创建时,将定义一个变量expectedModCount,并且把modCount的值赋给它;在List执行remove()时,modCount++;在Iterator执行next()迭代下一个元素时,做了一个if判断比较expectedModCount和modCount值时候相同,如果不同直接抛出ConcurrentModificationException异常:
在这里提供给大家两种解决方案:
方案一:在集合remove之前,迭代器也remove:
public class Test {
public static void main(String[] args) {
Set<String> set = new HashSet<String>();
set.add("one");
set.add("two");
set.add("three");
Iterator<String> it = set.iterator();
String str = null;
while (it.hasNext()) {
str = it.next();
it.remove();
set.remove(str);
}
System.out.println(set);
}
}
方案二:新建一个list,把需要迭代删除的元素保存进list在set遍历完成后调用removeAll(Object o)删除即可:
public class Test {
public static void main(String[] args) {
Set<String> set = new HashSet<String>();
set.add("one");
set.add("two");
set.add("three");
Iterator<String> it = set.iterator();
String str = null;
List<String> list = new ArrayList<String>();
while (it.hasNext()) {
str = it.next();
list.add(str);
}
set.removeAll(list);
System.out.println(set);
}
}
此外,其他如List等集合使用Iterator迭代删除时也会出现这样的异常,大家采用类似的解决方法即可。
因为时间问题,对本异常没有进行过深的分析,暂时也只想到这两种解决方案,不足之处希望大家补充,后续会抽时间深入研究并完善此文。