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应该做的伎俩而不是迭代集合 。 我觉得它也更快。