java 不能使用foreach_Java使用foreach遍历集和时不能add/remove的原因剖析

foreach 与 Iterator

我们知道,在Java中使用foreach对集和进行遍历时,是无法对该集和进行插入、删除等操作,比如以下代码:

for(Person p : personList){

if(StringUtil.isBlank(p.getName())){

personList.remove(p);

}

}

执行代码,报以下异常:

Exception in thread "main" java.util.ConcurrentModificationException

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

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

at com.xiuhao.service.ForeachDemo.main(ForeachDemo.java:20)

根据错误提示,定位ArrayList的源码,找到以下内容:

/**

* 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;

Itr() {}

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

}

}

...

final void checkForComodification() {

if (modCount != expectedModCount)

throw new ConcurrentModificationException();

}

}

即foreach的实现过程中使用Iterator的next()方法来实现遍历。在每次调用该方法前,首先执行checkForComodification()方法检查modCount和expectedModCount的值是否相等,如果不相等则直接抛出上文中的 ConcurrentModificationException

再来查看modCount和expectedModCount的值是如何定义的,在代码的开头部分初始化expectedModCount = modCount,即两者的值是相等的。modCount是ArrayList父类AbstractArrayList的成员变量,其定义如下:

/**

* The number of times this list has been structurally modified.

* 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.

*

*

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

* fail-fast behavior, rather than non-deterministic behavior in

* the face of concurrent modification during iteration.

*

*

Use of this field by subclasses is optional. 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;

由此可见,modCount纪录了有改变list大小等结构性变化或者其他使得遍历过程中产生不正确的结果的其它方式的次数,它的初始值为0,当每次迭代器被调用时,其值会被初始化成该list的大小。

当执行到personList.remove(p);时,查看remove()方法的源码:

/**

* Removes the element at the specified position in this list.

* Shifts any subsequent elements to the left (subtracts one from their

* indices).

*

* @param index the index of the element to be removed

* @return the element that was removed from the list

* @throws IndexOutOfBoundsException {@inheritDoc}

*/

public E remove(int index) {

rangeCheck(index);

modCount++;

E oldValue = elementData(index);

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

return oldValue;

}

发现该方法执行modCount++;,改变了值大小,当迭代器再次执行next()方法并调用checkForComodification()时,由于expectedModCount的值没有改变,因此会抛出 ConcurrentModificationException异常。同理,list的add方法同样会出发modCount++;,因此,无法使用foreach循环对list进行添加删除等操作。

那么,如何通过遍历进行list的增删操作呢,再次回到Iterator的源代码:

public void remove() {

if (lastRet < 0)

throw new IllegalStateException();

checkForComodification();

try {

ArrayList.this.remove(lastRet);

cursor = lastRet;

lastRet = -1;

expectedModCount = modCount; //重新设置expectedModCount

} catch (IndexOutOfBoundsException ex) {

throw new ConcurrentModificationException();

}

}

注意到Iterator的remove()方法重新设置了expectedModCount = modCount;,因此当再次执行next()时保证了两个参数一直相同,不会抛出异常,代码如下:

Iterator iterator = personList.iterator();

while (iterator.hasNext()) {

if(StringUtil.isBlank(iterator.next().getName())){

iterator.remove();

}

}

此外,对集和进行遍历编辑的方法包括:

直接使用普通for循环进行操作

int size = personList.size();

for(int i=0; i

if(StringUtil.isBlank(personList.get(i).getName())){

personList.remove(i);

}

}

使用Java 8中提供的filter过滤

List persons = personList.stream().filter(persron -> StringUtil.isNotBlank(persron.getName())).collect(Collectors.toList());

使用fail-safe的集合类,如ConcurrentHashMap等

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值