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

Posted

技术标签:

【中文标题】Java list .remove 方法仅适用于 foreach 循环中的倒数第二个对象[重复]【英文标题】:Java list's .remove method works only for second last object inside for each loop [duplicate] 【发布时间】:2013-04-11 09:20:56 【问题描述】:

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

    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 Certified Associate Java SE 7 Programmer Study Guide 2012”时引起了我的注意,该指南错误地假设 .remove() 始终适用于删除列表中倒数第二个的示例。

【问题讨论】:

其实我知道如何删除数组中的一个元素,但我想指出为什么它只适用于倒数第二个元素,这是一个不一致行为的错误吗?? 【参考方案1】:

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

“for each”循环的工作原理如下,

1.It gets the iterator.
2.Checks for hasNext().
public boolean hasNext() 

      return cursor != size(); // cursor is zero initially.

3.如果为true,则使用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() 也成功 无一例外。 cursor = 1 size = 5 --> hasNext() 成功并且 next() 也成功 无一例外。 cursor = 2 size = 5 --> hasNext() 成功并且 next() 也成功 无一例外。 cursor = 3 size = 5 --> hasNext() 成功并且 next() 也成功 毫无例外。

在您的情况下,当您删除 'd' 时,大小会减小到 4。

cursor = 4 size = 4 --> hasNext() 不成功,next() 是 跳过。

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

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

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

希望我说清楚了。

【讨论】:

【参考方案2】:

不要在此处使用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
    

【讨论】:

【参考方案3】:

由于 ArrayList 的快速失败行为而引发 ConcurrentException。这意味着除了 Iterator#remove() 之外,您在迭代列表时不能修改列表。

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

【讨论】:

【参考方案4】:

请使用Iterator#remove() 方法从List while 循环中删除元素。在内部,for-each 循环将使用Iterator 循环通过List 并且由于Iterator 的行为是未指定的,如果在迭代过程中以任何方式而不是通过调用Iterator's 来修改底层集合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=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

【讨论】:

【参考方案5】:

如果您真的想遍历 ArrayList 并删除元素,那么您应该这样做:

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

【讨论】:

【参考方案6】:

您可以“向后”迭代并删除元素,但不能向前。所以不是从第一个元素迭代到最后一个元素,而是从最后一个元素迭代到第一个元素。

伪代码:

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

   list.remove(i);

【讨论】:

【参考方案7】:

如果您希望全部删除。removeAll 应该做到这一点而不是遍历集合。我也觉得比较快。

【讨论】:

以上是关于Java list .remove 方法仅适用于 foreach 循环中的倒数第二个对象[重复]的主要内容,如果未能解决你的问题,请参考以下文章

java 的list.remove函数?

java list的remove方法问题

java list的remove方法问题

java list的remove方法问题

Java List --remove(int index)与remove(Object o)方法的区别

java中list.remove方法使用