List如何一边遍历,一边删除
1、foreach抛出ConcurrentModificationException
List<String> strList = new ArrayList<>();
strList.add("Java");
strList.add("Docker");
strList.add("SpringCloud");
for (String str : strList) {
if ("Java".equals(str)) {
strList.remove(str);
}
}
System.out.println(strList);
报错信息如下:
通过其生成的字节码可以看出,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
//初始化的时候expectedModCount等于modCount
int expectedModCount = modCount;
Itr() {}
public boolean hasNext() {
return cursor != size;
}
@SuppressWarnings("unchecked")
public E next() {
//调用checkForComodification,校验modCount是否等于expectedModCount,如果不等于抛出异常ConcurrentModificationException
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
,如果不等于抛出异常ConcurrentModificationException
在上面的案例中,刚开始modCount
和expectedModCount
的值都为3,所以第1次获取元素是没问题的,但是当执行完strList.remove(str)
时modCount
的值就被修改成了4
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; // clear to let GC do its work
}
所以在第2次获取元素时,modCount
和expectedModCount
的值就不相等了,所以抛出了异常ConcurrentModificationException
那么List一边遍历一边删除正确的实现方法有哪些呢?
1)、使用Iterator
的remove()
方法
2)、JDK1.8新增的removeIf()
方法
3)、使用for循环正序遍历
4)、使用for循环倒序遍历
2、使用Iterator
的remove()
方法
List<String> strList = new ArrayList<>();
strList.add("Java");
strList.add("Docker");
strList.add("SpringCloud");
Iterator<String> iterator = strList.iterator();
while (iterator.hasNext()) {
if ("Java".equals(iterator.next())) {
iterator.remove();
}
}
System.out.println(strList);
ArrayList中的Itr
实现源码如下:
public void remove() {
if (lastRet < 0)
throw new IllegalStateException();
checkForComodification();
try {
ArrayList.this.remove(lastRet);
cursor = lastRet;
lastRet = -1;
//expectedModCount、modCount这2个变量的值又相等了
expectedModCount = modCount;
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
每次删除一个元素,都会将modCount
的值重新赋值给expectedModCount
,这样2个变量就相等了
3、JDK1.8新增的removeIf()
方法
List<String> strList = new ArrayList<>();
strList.add("Java");
strList.add("Docker");
strList.add("SpringCloud");
strList.removeIf(str -> "Java".equals(str));
System.out.println(strList);
或者
List<String> strList = new ArrayList<>();
strList.add("Java");
strList.add("Docker");
strList.add("SpringCloud");
strList.removeIf("Java"::equals);
System.out.println(strList);
Collection接口中removeIf()
方法默认实现采用的也是Iterator
的remove()
方法
default boolean removeIf(Predicate<? super E> filter) {
Objects.requireNonNull(filter);
boolean removed = false;
final Iterator<E> each = iterator();
while (each.hasNext()) {
if (filter.test(each.next())) {
each.remove();
removed = true;
}
}
return removed;
}
4、使用for循环正序遍历
List<String> strList = new ArrayList<>();
strList.add("Java");
strList.add("Docker");
strList.add("SpringCloud");
for (int i = 0; i < strList.size(); ++i) {
String str = strList.get(i);
if ("Java".equals(str)) {
strList.remove(i--);
}
}
System.out.println(strList);
正序遍历需要修正下标,因为刚开始元素是:
0 Java
1 Docker
2 SpringCloud
第一次循环将Java删除后,元素的下标变成下面这样:
0 Docker
1 SpringCloud
如果不修正下标,第二次循环时i的值是1,也就是取到了SpringCloud,这样就导致元素Docker被跳过检查了
5、使用for循环倒序遍历
List<String> strList = new ArrayList<>();
strList.add("Java");
strList.add("Docker");
strList.add("SpringCloud");
for (int i = strList.size() - 1; i >= 0; --i) {
String str = strList.get(i);
if ("SpringCloud".equals(str)) {
strList.remove(i);
}
}
System.out.println(strList);
倒序遍历不需要修正下标,因为刚开始元素是:
0 Java
1 Docker
2 SpringCloud
第一次循环将SpringCloud删除后,元素的下标变成下面这样:
0 Java
1 Docker
第二次循环时i的值是1,也就是取到了元素Docker,不会导致跳过元素,所以不需要修正下标
参考:
https://blog.csdn.net/zwwhnly/article/details/104987143
上一篇: php多次引用出错