11

我看到一个奇怪的行为。

    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() 始终适用于删除列表中倒数第二个的示例。

4

7 回答 7

14

在列表中,添加或删除被视为修改。在您的情况下,您进行了 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。

如果我们从 list 中删除一个元素,它的大小会减小并且 modCount 会增加。

如果我们在迭代时移除一个元素,modCount != expectedModCount 会得到满足并抛出 ConcurrentModificationException。

但是删除倒数第二个对象很奇怪。让我们看看它在您的情况下是如何工作的。

最初,

cursor = 0 size = 5 --> hasNext() succeeds and next() also succeeds without exception.
cursor = 1 size = 5 --> hasNext() succeeds and next() also succeeds without exception.
cursor = 2 size = 5 --> hasNext() succeeds and next() also succeeds without exception.
cursor = 3 size = 5 --> hasNext() succeeds and next() also succeeds without exception.

In your case as you remove ‘d’ , size gets reduced to 4.

cursor = 4 size = 4 --> hasNext() does not succeed and next() is skipped.

In other cases, ConcurrentModificationException will be thrown as modCount != expectedModCount.

In this case, this check does not take place.

If you try to print your element while iterating, only four entries will be printed. Last element is skipped.

Hope I made clear.

于 2013-04-18T11:35:08.373 回答
5

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

而是使用Iterator#remove()从 List 中删除一个项目:

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
    }
}
于 2013-04-18T10:04:16.417 回答
2

请使用while 从循环中Iterator#remove()删除元素的方法。List在内部,for-each循环将使用Iterator来循环List和 ,因为Iterator如果在迭代过程中以任何方式修改底层集合,而不是通过调用 Iterator 的 remove() 方法,则 an 的行为是未指定的。你得到了异常。

这个循环:

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, so的hasNext()计算结果为false并且循环在 中执行并发修改检查之前提前中断next()。在这种情况下,永远不会访问最后一个元素。您可以通过在if

    if(str.equalsIgnoreCase("d") || str.equalsIgnoreCase("e")){
        // last element "e" won't be removed as it is not accessed
        li.remove(str);   
    }

但是,如果您删除任何其他元素next(),则会引发ConcurrentModificationException.

于 2013-04-18T10:03:47.317 回答
2

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

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

于 2013-04-18T10:05:35.110 回答
0

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

for(int index = yourArrayList.size() - 1; index >= 0; index--) {
    if(yourCondition) {
        yourArrayList.remove(index);
    }
}
于 2013-04-18T10:08:17.557 回答
0

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

伪代码:

for(int i = list.size() -1; i >= 0; i--)
{
   list.remove(i);
}
于 2013-04-18T10:10:13.610 回答
0

If you wish to remove all then.removeAll should do the trick rather than iterating through the collection. I think it is speedier too.

于 2013-10-25T06:17:34.047 回答