首先来分析反例,反编译后的代码
List<String> list = new ArrayList();
list.add("沉默王二");
list.add("沉默王三");
list.add("一个文章真特么有趣的程序员");
Iterator var2 = list.iterator();
while(var2.hasNext()) {
String str = (String)var2.next();
if ("沉默王二".equals(str)) {
list.remove(str);
}
}
System.out.println(list);
ArrayList 实现了Itertor接口
public Iterator<E> iterator() {
return new ArrayList.Itr();
}
Itr是其中的一个内部类,用来真正的进行遍历等操作。
private class Itr implements Iterator<E> {
int cursor;
int lastRet = -1;
int expectedModCount;
Itr() {
this.expectedModCount = ArrayList.this.modCount;
}
public boolean hasNext() {
return this.cursor != ArrayList.this.size;
}
public E next() {
this.checkForComodification();
int i = this.cursor;
if (i >= ArrayList.this.size) {
throw new NoSuchElementException();
} else {
Object[] elementData = ArrayList.this.elementData;
if (i >= elementData.length) {
throw new ConcurrentModificationException();
} else {
this.cursor = i + 1;
return elementData[this.lastRet = i];
}
}
}
public void remove() {
if (this.lastRet < 0) {
throw new IllegalStateException();
} else {
this.checkForComodification();
try {
ArrayList.this.remove(this.lastRet);
this.cursor = this.lastRet;
this.lastRet = -1;
this.expectedModCount = ArrayList.this.modCount;
} catch (IndexOutOfBoundsException var2) {
throw new ConcurrentModificationException();
}
}
}
public void forEachRemaining(Consumer<? super E> action) {
Objects.requireNonNull(action);
int size = ArrayList.this.size;
int i = this.cursor;
if (i < size) {
Object[] es = ArrayList.this.elementData;
if (i >= es.length) {
throw new ConcurrentModificationException();
}
while(i < size && ArrayList.this.modCount == this.expectedModCount) {
action.accept(ArrayList.elementAt(es, i));
++i;
}
this.cursor = i;
this.lastRet = i - 1;
this.checkForComodification();
}
}
final void checkForComodification() {
if (ArrayList.this.modCount != this.expectedModCount) {
throw new ConcurrentModificationException();
}
}
}
元素报错案例
List<String> list = new ArrayList<>();
list.add("沉默王二");
list.add("沉默王三");
list.add("一个文章真特么有趣的程序员");
for (String str : list) {
if ("沉默王二".equals(str)) {
list.remove(str);
}
}
System.out.println(list);
显示这个错误,深入探讨一下
即在重写了iterator的arraylist的Itr类的remove方法前会先判断修改次数与期望修改的次数是否相同,不相同就会报出该错误
由于 list 此前执行了 3 次 add 方法。
- add 方法调用 ensureCapacityInternal 方法
- ensureCapacityInternal 方法调用 ensureExplicitCapacity 方法
- ensureExplicitCapacity 方法中会执行
modCount++
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
}
所以 modCount 的值在经过三次 add 后为 3,于是 new Itr() 后 expectedModCount 的值也为 3(回到前面去看一下 Itr 的源码)。
接着来执行 for-each 的循环遍历。
执行第一次循环时,发现“沉默王二”等于 str,于是执行 list.remove(str)。
- remove 方法调用 fastRemove 方法
- fastRemove 方法中会执行
modCount++
private void fastRemove(int index) {
modCount++;
}
modCount 的值变成了 4。
第二次遍历时,会执行 Itr 的 next 方法(String str = (String) var3.next();),next 方法就会调用 checkForComodification 方法。
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
此时 expectedModCount 为 3,modCount 为 4,就只好抛出 ConcurrentModificationException 异常了。
使用Iterator
List<String> list = new ArrayList<>();
list.add("沉默王二");
list.add("沉默王三");
list.add("一个文章真特么有趣的程序员");
Iterator<String> itr = list.iterator();
while (itr.hasNext()) {
String str = itr.next();
if ("沉默王二".equals(str)) {
itr.remove();
}
}
这个为什么没有问题? 因为
使用iteartor的remove方法最后会将这两个值变为相同的,下一次next时检查也不会出错。 为什么不能在foreach里执行删除操作?
因为 foreach 循环是基于迭代器实现的,而迭代器在遍历集合时会维护一个 expectedModCount 属性来记录集合被修改的次数。如果在 foreach 循环中执行删除操作会导致 expectedModCount 属性值与实际的 modCount 属性值不一致,从而导致迭代器的 hasNext() 和 next() 方法抛出 ConcurrentModificationException 异常。
为了避免这种情况,应该使用迭代器的 remove() 方法来删除元素,该方法会在删除元素后更新迭代器状态,确保循环的正确性。如果需要在循环中删除元素,应该使用迭代器的 remove() 方法,而不是集合自身的 remove() 方法。
就像这样。
List<String> list = new ArrayList<>();
list.add("沉默王二");
list.add("沉默王三");
list.add("一个文章真特么有趣的程序员");
Iterator<String> itr = list.iterator();
while (itr.hasNext()) {
String str = itr.next();
if ("沉默王二".equals(str)) {
itr.remove();
}
}
除此之外,我们还可以采用 Stream 流的filter() 方法来过滤集合中的元素,然后再通过 collect() 方法将过滤后的元素收集到一个新的集合中。
List<String> list = new ArrayList<>(Arrays.asList("沉默", "王二", "陈清扬"));
list = list.stream().filter(s -> !s.equals("陈清扬")).collect(Collectors.toList());
同时也可以使用以下两种方法
1)remove 后 break
List<String> list = new ArrayList<>();
list.add("沉默王二");
list.add("沉默王三");
list.add("一个文章真特么有趣的程序员");
for (String str : list) {
if ("沉默王二".equals(str)) {
list.remove(str);
break;
}
}
break 后循环就不再遍历了,意味着 Iterator 的 next 方法不再执行了,也就意味着 checkForComodification 方法不再执行了,所以异常也就不会抛出了。
但是呢,当 List 中有重复元素要删除的时候,break 就不合适了。
2)for 循环
List<String> list = new ArrayList<>();
list.add("沉默王二");
list.add("沉默王三");
list.add("一个文章真特么有趣的程序员");
for (int i = 0; i < list.size(); i++) {
String str = list.get(i);
if ("沉默王二".equals(str)) {
list.remove(str);
}
}
for 循环虽然可以避开 fail-fast 保护机制,也就说 remove 元素后不再抛出异常;但是呢,这段程序在原则上是有问题的。为什么呢?
第一次循环的时候,i 为 0,list.size() 为 3,当执行完 remove 方法后,i 为 1,list.size() 却变成了 2,因为 list 的大小在 remove 后发生了变化,也就意味着“沉默王三”这个元素被跳过了。能明白吗?
remove 之前 list.get(1) 为“沉默王三”;但 remove 之后 list.get(1) 变成了“一个文章真特么有趣的程序员”,而 list.get(0) 变成了“沉默王三”
可以考虑一下for循环如果解决这个问题,从后往前。