list foreach方法_foreach循环中为什么不要进行remove/add操作

快,关注这个号,一起涨姿势~

a9231ad926dc54a7b0496556b411a433.png

foreach循环中为什么不要进行remove/add操作

从阿里的代码规范中我们可清楚看到foreach循环中不要remove/add操作字眼。具体见下图:

ea85032f15c79c16310a60a12ba80bbc.png

接下来,我们不妨试一试,看看究竟吧,大家跟我来!!!

一.从阿里规范中看代码

我们把阿里的代码摘下来敲一敲:

List list = new ArrayList();

list.add("1");

list.add("2");

for (String item : list) {

if ("1".equals(item)) {

list.remove(item);

}

}

我们在idea执行一下,没有问题,但是需要注意,此时循环只执行了一次。再来看一段会出问题的代码:

List list = new ArrayList();

list.add("1");

list.add("2");

for (String item : list) {

if ("2".equals(item)) {

list.remove(item);

}

}

控制台输出为:

Exception in thread "main" java.util.ConcurrentModificationException

at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:901)

at java.util.ArrayList$Itr.next(ArrayList.java:851)

at Foreach.main(Foreach.java:16)

异常出现了,原因是什么昵?我们接着分析!!!

二.怎么回事昵?

从上看是不是很奇怪?真是纳闷呀!!!,接下来对class文件,反编译下(idea支持反编译,点一下.class文件即可查看),代码如下:

List list = new ArrayList();

list.add("1");

list.add("2");

Iterator var2 = list.iterator();

while(var2.hasNext()) {

String item = (String)var2.next();

if ("2".equals(item)) {

list.remove(item);

}

}

可以发现,原本的增强for循环,其实是依赖了while循环和Iterator实现的。原因还没出,我们慢慢往下看。

在往下看代码之前我们先牢记几个词:

  • modCount是ArrayList中的一个成员变量。它表示该集合实际被修改的次数。
  • expectedModCount 是 ArrayList中的一个内部类——Itr中的成员变量。expectedModCount表示这个迭代器期望该集合被修改的次数。其值是在ArrayList.iterator方法被调用的时候初始化的。只有通过迭代器对集合进行操作,该值才会改变。
  • Itr是一个Iterator的实现,使用ArrayList.iterator方法可以获取到的迭代器就是Itr类的实例。

(1)我们进去list.remove()方法及查看核心方法fastRemove()。

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

}

由上可以看到list做remove操作,modCount是改变的,也就是操作一次累加1。

(2)顺路观察下list.add()方法

public boolean add(E e) {

ensureCapacityInternal(size + 1); // Increments modCount!!

elementData[size++] = e;

return true;

}

这个方法modCount也会累加。

(3)再去观察下,iterator()方法、Iterator的实现、checkForComodification()方法

public Iterator iterator() {

return new Itr();

}

/**

* An optimized version of AbstractList.Itr

*/

private class Itr implements Iterator {

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();

}

}

简单总结一下,ArrayList是线程不安全的,在被修改后再继续迭代就报错。由Iterator实现可以看到,当迭代时,会将modCount暂存在expectedModCount中,

在获取下一个元素时,都检查修改次数是否有变动,有变动则不再继续迭代,而是抛出错误ConcurrentModificationException。

好了,大体原因就是这样了。然而这个问题要如何避免昵?我们看一下第三个部分即可。

三、如何避免

(1)直接使用普通for循环进行操作,代码如下:

List list = new ArrayList();

list.add("1");

list.add("2");

for (int i = 0; i < list.size(); i++) {

if ("2".equals(list.get(i))) {

list.remove(list.get(i));

}

}

(2)直接使用Iterator进行操作,阿里推荐,代码如下:

List list = new ArrayList();

list.add("1");

list.add("2");

Iterator iterator = list.iterator();

while (iterator.hasNext()) {

if (iterator.next().equals("2")) {

iterator.remove();

}

}

(3)使用Java 8中提供的filter过滤

List list = new ArrayList();

list.add("1");

list.add("2");

ist = list.stream().filter(l -> !l.equals("2")).collect(Collectors.toList());

(4)直接使用fail-safe的集合类

fail-fast,即快速失败,它是Java集合的一种错误检测机制。当多个线程对集合(非fail-safe的集合类)进行结构上的改变的操作时,有可能会产生fail-fast机制,这个时候就会抛出ConcurrentModificationException(当方法检测到对象的并发修改,但不允许这种修改时就抛出该异常)。

同时需要注意的是,即使不是多线程环境,如果单线程违反了规则,同样也有可能会抛出改异常。

代码如下:

ConcurrentLinkedDeque list = new ConcurrentLinkedDeque() {{

add("1");

add("2");

}};

for (String item : list) {

if ("2".equals(item)) {

list.remove(item);

}

}

(5)使用增强for循环其实也可以

如果,我们非常确定在一个集合中,某个即将删除的元素只包含一个的话, 比如对Set进行操作,那么其实也是可以使用增强for循环的,只要在删除之后,立刻结束循环体,不要再继续进行遍历就可以了,也就是说不让代码执行到下一次的next方法。

代码如下:

List list = new ArrayList<>();

list.add("1");

list.add("2");

for (String item : list) {

if ("1".equals(item)) {

list.remove(item);

}

}

上面这个例子较特殊,也就是第二次循环它进不去了,非特殊例子,大家可用break结束。

四、针对上述内容做个小结

我们使用的增强for循环,其实是Java提供的语法糖,其实现原理是借助Iterator进行元素的遍历。

但是如果在遍历过程中,不通过Iterator,而是通过增强for循环,集合类自身的方法对集合进行添加/删除操作。那么在Iterator进行下一次的遍历时,经检测发现有一次集合的修改操作并未通过自身进行,那么可能是发生了并发被其他线程执行的,这时候就会抛出异常,来提示用户可能发生了并发修改,这就是所谓的fail-fast机制。

当然还是有很多种方法可以解决这类问题的。比如使用普通for循环、使用Iterator进行元素删除、使用Stream的filter、使用fail-safe的类、使用增强for

循环也可以但要注意事项等。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值