假如有一个Map和一个Set,我们要删除Map中key在Set里面的元素,这种情况只需遍历Set,然后分别删除Map中对应的元素即可。但假如我们要删除Map中key不在Set里面的元素,这时就不能遍历Set了,此时需要遍历Map,如下:
Map<String, String> map = new HashMap<>();
map.put("a", "a");
map.put("b", "b");
map.put("c", "c");
map.put("d", "d");
Set<String> set = new HashSet<>();
set.add("b");
set.add("c");
//遍历map,删除key不在set里面的元素
for(String key:map.keySet()) {
if(!set.contains(key))
map.remove(key);
}
但很可惜,这个代码是错的,我们不能在遍历Map/Set的同时删除被遍历对象的元素,否则会报错:
ConcurrentModificationException
我们可以通过Iterator来实现:
Map<String, String> map = new HashMap<>();
map.put("a", "a");
map.put("b", "b");
map.put("c", "c");
map.put("d", "d");
Set<String> set = new HashSet<>();
set.add("b");
set.add("c");
//遍历map,删除key不在set里面的元素
Iterator<String>iterator = map.keySet().iterator();
while (iterator.hasNext()) {
String key = iterator.next();
if(!set.contains(key))
iterator.remove();
}
报ConcurrentModificationException错误的原因如下:
ArrayList为例来分析:
在ArrayList里,有一个变量:protected transient int modCount = 0;
上面这个变量表示这个集合被结构性修改的次数。什么是结构性修改?看代码就会发现,是指add()、remove()、clear()、addAll()等增、删集合元素的动作。每当执行一次这些动作,上面的modCount变量就会加一,如下面代码展示:
public boolean add(E e) {
modCount++;
add(e, elementData, size);
return true;
}
那么这个变量有什么用呢?通过代码发现,在使用Iterator迭代的时候会用到这个变量,如下面代码展示
//ArrayList里的iterator()方法
public synchronized Iterator<E> iterator() {
return new Itr();
}
/**
* 上面方法会创建这个Itr对象
* An optimized version of AbstractList.Itr
*/
private class Itr implements Iterator<E> {
int cursor; // index of next element to return
int lastRet = -1; // index of last element returned; -1 if no such
//创建的时候,记录当前时间点集合被修改过的次数
int expectedModCount = modCount;
public boolean hasNext() {
// Racy but within spec, since modifications are checked
// within or after synchronization in next/previous
return cursor != elementCount;
}
public E next() {
synchronized (Vector.this) {
//校验集合是否被修改过
checkForComodification();
int i = cursor;
if (i >= elementCount)
throw new NoSuchElementException();
cursor = i + 1;
return elementData(lastRet = i);
}
}
public void remove() {
if (lastRet == -1)
throw new IllegalStateException();
synchronized (Vector.this) {
checkForComodification();
Vector.this.remove(lastRet);
expectedModCount = modCount;
}
cursor = lastRet;
lastRet = -1;
}
@Override
public void forEachRemaining(Consumer<? super E> action) {
Objects.requireNonNull(action);
synchronized (Vector.this) {
final int size = elementCount;
int i = cursor;
if (i >= size) {
return;
}
final Object[] es = elementData;
if (i >= es.length)
throw new ConcurrentModificationException();
while (i < size && modCount == expectedModCount)
action.accept(elementAt(es, i++));
// update once at end of iteration to reduce heap write traffic
cursor = i;
lastRet = i - 1;
checkForComodification();
}
}
//校验当前时间点此集合的修改次数是否跟本对象创建时记录的次数一致,
//如果不一致,则抛出ConcurrentModificationException异常。
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}
另外,由此我们也看到,产生ConcurrentModificationException的地方就在这里,就是在Itr的next()、remove()、forEachRemaining()里面,它们都会校验集合是否被修改,如果有修改,则抛出ConcurrentModificationException异常。校验的原理也很简单,就是对比当前的集合的修改次数与当时Itr对象创建时记录的次数,如果不一致,就是被修改过了。
因此,在实际开发过程中,如果使用Iterator来对ArrayList这种集合进行迭代的过程中,对该集合进行了增、删元素操作,然后等下一轮走到next()方法时,next方法就会检测到集合的修改,就会抛出这个ConcurrentModificationException异常。