Java list的.remove方法仅适用于每个循环内部的倒数第二个对象
我看到一种奇怪的行为。
List li = new ArrayList(); li.add("a"); li.add("b"); li.add("c"); li.add("d"); li.add("e"); for(String str:li){ if(str.equalsIgnoreCase("d")){ li.remove(str); //removing second last in list works fine } }
但是,如果我尝试删除列表中的第二个以外的任何其他内容,我会收到ConcurrentModificationException。 在阅读“Oracle认证助理Java SE 7程序员学习指南2012”时,我注意到它错误地假定.remove()始终使用删除列表中倒数第二个的示例。
在列表中,添加或删除被视为修改。 在您的情况下,您已经进行了5次修改(添加)。
‘for each’循环的工作原理如下,
1.It gets the iterator. 2.Checks for hasNext().
public boolean hasNext() { return cursor != size(); // cursor is zero initially. }
3.如果是,请使用next()获取下一个元素。
public E next() { checkForComodification(); try { E next = get(cursor); lastRet = cursor++; return next; } catch (IndexOutOfBoundsException e) { checkForComodification(); throw new NoSuchElementException(); } } final void checkForComodification() { // Initially modCount = expectedModCount (our case 5) if (modCount != expectedModCount) throw new ConcurrentModificationException(); }
重复步骤2和3,直到hasNext()返回false。
如果我们从列表中删除一个元素,它的大小会减少,modCount会增加。
如果我们在迭代时删除一个元素,则modCount!= expectedModCount得到满足并抛出ConcurrentModificationException。
但删除倒数第二个对象很奇怪。 让我们看看它在你的情况下是如何工作的。
原来,
cursor = 0 size = 5 – > hasNext()成功,next()也成功,无exception。
cursor = 1 size = 5 – > hasNext()成功,next()也成功,无exception。
cursor = 2 size = 5 – > hasNext()成功,next()也成功,无exception。
cursor = 3 size = 5 – > hasNext()成功,next()也成功,无exception。
在您删除“d”的情况下,大小会减少到4。
cursor = 4 size = 4 – > hasNext()不成功,跳过next()。
在其他情况下,ConcurrentModificationException将作为modCount!= expectedModCount抛出。
在这种情况下,不会进行此检查。
如果在迭代时尝试打印元素,则只打印四个条目。 跳过最后一个元素。
希望我说清楚。
Don在这里使用List#remove(Object)
,因为你在for-each循环中访问List中的元素。
而是使用Iterator #remove()从List中删除一个项目:
for(Iterator it=li.iterator(); it.hasNext();) { String str = it.next(); if(str.equalsIgnoreCase("d")) { it.remove(); //removing second last in list works fine } }
在循环时从List
删除元素时,请使用Iterator#remove()
方法。 在内部, for-each
循环将使用Iterator
循环遍历List
,因为如果在迭代正在进行中以除了调用Iterator的remove()方法之外的任何方式修改底层集合时,未指定Iterator
的行为。你是正在获得例外。
这个循环:
for(String str:li){ if(str.equalsIgnoreCase("d")){ li.remove(str); //removing second last in list works fine } }
基本上是
Iterator itr = li.iterator(); while(itr.hasNext()){ String str = (String)itr.next(); if(str.equalsIgnoreCase("d")){ li.remove(str); //removing second last in list works fine } }
为什么删除倒数第二个元素不会抛出exception?
因为通过删除第二个最后一个元素,您已将大小减小到已迭代的元素数。 一个基本的hasNext()
实现是
public boolean hasNext() { return cursor != size; }
因此,在这种情况下, cursor=size=4
,因此hasNext()
计算结果为false
并且在next()
执行并发修改检查之前,循环会中断。 在这种情况下永远不会访问最后一个元素。 你可以通过在if
添加一个简单的OR条件来检查它
if(str.equalsIgnoreCase("d") || str.equalsIgnoreCase("e")){ // last element "e" won't be removed as it is not accessed li.remove(str); }
但是如果你删除任何其他元素next()
被调用,则抛出ConcurrentModificationException
。
由于ArrayList的快速失败行为,引发了ConcurrentException。 这意味着除了Iterator #remove()之外,您无法在迭代时修改列表。
请参阅http://docs.oracle.com/javase/7/docs/api/java/util/ArrayList.html
如果你真的想迭代一个ArrayList
并删除元素,那么你应该这样做:
for(int index = yourArrayList.size() - 1; index >= 0; index--) { if(yourCondition) { yourArrayList.remove(index); } }
您可以“向后”迭代并删除元素,但不能转发。 因此,不是从第一个元素迭代到最后一个元素,而是从最后一个元素迭代到第一个元素。
伪代码:
for(int i = list.size() -1; i >= 0; i--) { list.remove(i); }
如果你想删除所有。 removeAll应该做的伎俩而不是迭代集合 。 我觉得它也更快。