分析
首先你可能会想到使用下标报错,那就就不使用下标foreach来解决,于是下边代码出现
List<String> platformList = new ArrayList<>();
platformList.add("博客园");
platformList.add("CSDN");
platformList.add("掘金");
platformList.add("简书");
for (String platform : platformList) {
if (platform.equals("博客园")) {
platformList.remove(platform);
}
}
System.out.println(platformList);
很可惜,酱紫会报错
"C:\Program Files\Java\jdk1.8.0_151\bin\java" "-javaagent:C:\Program ......
Exception in thread "main" java.util.ConcurrentModificationException
at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:907)
at java.util.ArrayList$Itr.next(ArrayList.java:857)
at com.suyl.demo.test.ListTest.main(ListTest.java:21)
把编译的class文件用idea打开发现,此段代码生成的字节码为
List<String> platformList = new ArrayList();
platformList.add("博客园");
platformList.add("CSDN");
platformList.add("掘金");
platformList.add("简书");
Iterator var2 = platformList.iterator();
while(var2.hasNext()) {
String platform = (String)var2.next();
if (platform.equals("博客园")) {
platformList.remove(platform);
}
}
System.out.println(platformList);
由此可以看出,foreach循环在实际执行时,其实使用的是Iterator,使用的核心方法是hasnext()和next()。
接下来看ArrayList类的Iterator是如何实现的
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() {
return cursor != size;
}
@SuppressWarnings("unchecked")
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];
}
public void remove() {
if (lastRet < 0)
throw new IllegalStateException();
checkForComodification();
try {
ArrayList.this.remove(lastRet);
cursor = lastRet;
lastRet = -1;
expectedModCount = modCount;
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
@Override
@SuppressWarnings("unchecked")
public void forEachRemaining(Consumer<? super E> consumer) {
Objects.requireNonNull(consumer);
final int size = ArrayList.this.size;
int i = cursor;
if (i >= size) {
return;
}
final Object[] elementData = ArrayList.this.elementData;
if (i >= elementData.length) {
throw new ConcurrentModificationException();
}
while (i != size && modCount == expectedModCount) {
consumer.accept((E) elementData[i++]);
}
// update once at end of iteration to reduce heap write traffic
cursor = i;
lastRet = i - 1;
checkForComodification();
}
final void checkForComodification() { // 重点看这里
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}
可以看出,调用next()方法获取下一个元素时,第一行代码就是调用了checkForComodification();,而该方法的核心逻辑就是比较modCount和expectedModCount这2个变量的值。
在上面的例子中,刚开始modCount和expectedModCount的值都为4,所以第1次获取元素"博客园"是没问题的,但是当执行完下面这行代码时:
platformList.remove(platform);
modCount的值就被修改成了5。
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 remove method that skips bounds checking and does not
* return the value removed.
*/
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; // clear to let GC do its work
}
所以在第2次获取元素时,modCount和expectedModCount的值就不相等了,所以抛出了java.util.ConcurrentModificationException异常。
实现的方式:
-
使用Iterator的remove()方法
-
使用for循环正序遍历
-
使用for循环倒序遍历
使用Iterator的remove()方法
List<String> platformList = new ArrayList<>();
platformList.add("博客园");
platformList.add("CSDN");
platformList.add("掘金");
platformList.add("简书");
Iterator<String> iterator = platformList.iterator();
while (iterator.hasNext()) {
String platform = iterator.next();
if (platform.equals("博客园")) {
iterator.remove();
}
}
System.out.println(platformList);
结果为:
[CSDN, 掘金, 简书]
为什么使用iterator.remove();就可以呢?
public void remove() {
if (lastRet < 0)
throw new IllegalStateException();
checkForComodification();
try {
ArrayList.this.remove(lastRet);
cursor = lastRet;
lastRet = -1;
expectedModCount = modCount; // 重点看这里
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
可以看出,每次删除一个元素,都会将modCount的值重新赋值给expectedModCount,这样2个变量就相等了,不会触发java.util.ConcurrentModificationException异常
使用for循环正序遍历
List<String> platformList = new ArrayList<>();
platformList.add("博客园");
platformList.add("CSDN");
platformList.add("掘金");
platformList.add("简书");
for (int i = 0; i < platformList.size(); i++) {
String item = platformList.get(i);
if (item.equals("博客园")) {
platformList.remove(i);
i = i - 1;
}
}
System.out.println(platformList);
这种实现方式比较好理解,就是通过数组的下标来删除,不过有个注意事项就是删除元素后,要修正下下标的值
使用for循环倒序遍历
List<String> platformList = new ArrayList<>();
platformList.add("博客园");
platformList.add("CSDN");
platformList.add("掘金");
platformList.add("简书");
for (int i = platformList.size() - 1; i >= 0; i--) {
String item = platformList.get(i);
if (item.equals("掘金")) {
platformList.remove(i);
}
}
System.out.println(platformList);
这种实现方式和使用for循环正序遍历类似,不过不用再修正下标