在一个暴雨连连的午后,我如同往常一样看着项目日志,后台忽然发现某个任务报了ConcurrentModificationException错误,由于没啥经验,网上找了一波资料才发现这个错误原来是以前八股文里遇到过的错误😂,果然实践才能出真知,下面将用输出的方式加深一下记忆。
1、错误的诞生
首先介绍容器遍历时存在的两种类型:快速失败
和 安全失败
具体的区别:快速失败在遍历时无法对当前集合进行修改,常见的java.util下的集合都是快速失败类型
具体原理看源码:
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();
}
我们发现,一个关键方法时,是在迭代时的checkForComodification
会对集合本身的modCount进行判断,那么这modCount是什么呢?
/**
* The number of times this list has been <i>structurally modified</i>.
* Structural modifications are those that change the size of the
* list, or otherwise perturb it in such a fashion that iterations in
* progress may yield incorrect results.
*
* <p>This field is used by the iterator and list iterator implementation
* returned by the {@code iterator} and {@code listIterator} methods.
* If the value of this field changes unexpectedly, the iterator (or list
* iterator) will throw a {@code ConcurrentModificationException} in
* response to the {@code next}, {@code remove}, {@code previous},
* {@code set} or {@code add} operations. This provides
* <i>fail-fast</i> behavior, rather than non-deterministic behavior in
* the face of concurrent modification during iteration.
*
* <p><b>Use of this field by subclasses is optional.</b> If a subclass
* wishes to provide fail-fast iterators (and list iterators), then it
* merely has to increment this field in its {@code add(int, E)} and
* {@code remove(int)} methods (and any other methods that it overrides
* that result in structural modifications to the list). A single call to
* {@code add(int, E)} or {@code remove(int)} must add no more than
* one to this field, or the iterators (and list iterators) will throw
* bogus {@code ConcurrentModificationExceptions}. If an implementation
* does not wish to provide fail-fast iterators, this field may be
* ignored.
*/
protected transient int modCount = 0;
翻译过来就是,这个变量表示了该集合发生元素数量变化的次数。
2、错误解决
且看代码
public static void test() {
System.out.println("元素遍历方式");
List<String> list = new ArrayList<>();
IntStream.range(0, 26).forEach(i -> {
list.add((char) ('a' + i) + "");
});
System.out.println(list);
for (int i = 0; i < list.size(); i++) {
if (list.get(i).equals("z")) list.remove(list.get(i));
}
System.out.println(list);
}
public static void test1() {
System.out.println("迭代器方式修改");
List<String> list = new ArrayList<>();
IntStream.range(0, 26).forEach(i -> {
list.add((char) ('a' + i) + "");
});
System.out.println(list);
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
String next = iterator.next();
if(next.equals("z")){
iterator.remove();
}
}
System.out.println(list);
}
public static void test2() {
System.out.println("removeIf修改");
List<String> list = new ArrayList<>();
IntStream.range(0, 26).forEach(i -> {
list.add((char) ('a' + i) + "");
});
System.out.println(list);
list.removeIf(e -> e.equals("z"));
System.out.println(list);
}
输出
3、总结
①、实践出真知
②、removeIf应该是比较便捷的解决方案