Java list .remove方法仅适用于foreach循环中的倒数第二个对象

时间:2013-04-18 10:01:41

标签: java foreach

我看到了一种奇怪的行为。

    List<String> 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()总是使用删除列表中倒数第二个的示例。

7 个答案:

答案 0 :(得分:13)

在列表中,添加或删除被视为修改。在你的情况下,你做了 5个修改(补充)。

每个'循环的

'的工作原理如下,

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 - &gt; hasNext()成功,next()也成功   毫无例外。
  cursor = 1 size = 5 - &gt; hasNext()成功,next()也成功   毫无例外。
  cursor = 2 size = 5 - &gt; hasNext()成功,next()也成功   毫无例外。
  cursor = 3 size = 5 - &gt; hasNext()成功,next()也成功   没有例外。

在你删除'd'的情况下,大小会减少到4。

  

cursor = 4 size = 4 - &gt; hasNext()不成功,next()不成功   跳过。

在其他情况下,ConcurrentModificationException将作为modCount!= expectedModCount抛出。

在这种情况下,不会进行此项检查。

如果您尝试在迭代时打印元素,则只会打印四个条目。跳过最后一个元素。

希望我说清楚。

答案 1 :(得分:6)

Don在这里使用List#remove(Object),因为您在for-each循环中访问List中的元素。

而是使用Iterator#remove()从列表中删除项目:

for(Iterator<String> it=li.iterator(); it.hasNext();) {
    String str = it.next();
    if(str.equalsIgnoreCase("d")) {
        it.remove();     //removing second last in list works fine
    }
}

答案 2 :(得分:3)

请在循环时从Iterator#remove()删除元素时使用List方法。在内部,for-each循环将使用Iterator循环遍历List,因为如果在迭代进行过程中修改了基础集合,则未指定Iterator的行为除了通过调用Iterator的remove()方法之外的任何其他方式。你得到了异常。

这个循环:

for(String str:li){
    if(str.equalsIgnoreCase("d")){
       li.remove(str);     //removing second last in list works fine
     }
}

基本上是

Iterator<String> 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
    }
}

为什么删除第二个最后一个元素不会抛出异常?

因为通过删除第二个最后一个元素,您已将大小减小到迭代的元素数。基本的hasNext()实施是

    public boolean hasNext() {
       return cursor != size;
    }

因此,在这种情况下cursor=size=4hasNext()评估为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

答案 3 :(得分:2)

由于ArrayList的快速失败行为而引发ConcurrentException。这意味着除了Iterator #remove()。

之外,您无法在迭代时修改列表

参考http://docs.oracle.com/javase/7/docs/api/java/util/ArrayList.html

答案 4 :(得分:0)

如果您真的要迭代ArrayList并删除元素,那么您应该这样做:

for(int index = yourArrayList.size() - 1; index >= 0; index--) {
    if(yourCondition) {
        yourArrayList.remove(index);
    }
}

答案 5 :(得分:0)

你可以向后迭代&#34;并删除元素,但不转发。因此,不是从第一个元素迭代到最后一个元素,而是从最后一个元素迭代到第一个元素。

的伪代码:

for(int i = list.size() -1; i >= 0; i--)
{
   list.remove(i);
}

答案 6 :(得分:0)

如果您希望删除所有内容。 removeAll 应该执行操作,而不是遍历集合。我觉得它也更快。