2
public class Alpha {

    public static void main(String[] args) {
        ArrayList<String> al = new ArrayList<String>();

        al.add("a");
        al.add("b");
        al.add("c");
        al.add("d");
        al.add("e");


        Iterator<String> itr = al.listIterator();
        while(itr.hasNext()){
            al.remove("d"); // Throws a Concurrent Modification @ this line
            if(itr.next().equals("d")){
                //itr.remove();
                al.remove("d"); // No error on this line
            }

        }
        System.out.println(al);

    }

}

换句话说,如果将 "al.remove("d") 放在 if 构造中,则不会抛出 ConcurrentModificationException,与同一行代码一样,如果放在 if -Construct 之外,则会引发异常。请解释!

4

7 回答 7

2

这是因为当你使用 获取下一个元素时itr.next(),它会检查修改并检查集合的大小是否已更改。那时if (modCount != expectedModCount)条件将变为真实。而hasNext()方法仅根据当前光标点返回 true 或 false。

如果你喜欢,itr.next()然后在列表上调用 remove ,那么它也会更新expectedModCount变量。检查Arraylist 迭代器的remove方法。

于 2014-12-29T07:27:57.617 回答
0

重复: 遍历列表,在循环中删除时避免 ConcurrentModificationException 在迭代时 从 Java 中的集合中删除项目

只是你不能在迭代中删除一个元素。

于 2014-12-29T07:29:56.300 回答
0

看看ArrayList迭代器是如何实现的:

public void remove() {
if (lastRet < 0)
    throw new IllegalStateException();
checkForComodification();

try {
    ArrayList.this.remove(lastRet);
    cursor = lastRet;
    lastRet = -1;
    expectedModCount = modCount;
 } catch (IndexOutOfBoundsException ex) {
    throw new ConcurrentModificationException();
 }
}

因此它检查并发修改,使用公共 ArrayList remove 方法删除元素,并增加列表修改的计数器,以便在下一次迭代时不会抛出 ConcurrentModificationException。

于 2014-12-29T07:31:14.600 回答
0

就像您正在修改其他人当前正在使用的文件(此处为 ArrayList)(此处为迭代循环)。

于 2014-12-29T07:31:23.037 回答
0
while(itr.hasNext()){

            if(itr.next().equals("d"))
            {
                //itr.remove();
                al.remove("d"); // No error on this line
            }

        }
        System.out.println(al);

使用这行代码。希望这会有所帮助

于 2014-12-29T07:40:44.957 回答
0

我添加了一行(第 13 行)和一些注释行号(第 18 行、第 19 行和第 21 行)。

现在,ConcurrentModidificationexception 没有在第 18 行抛出,而是在第 19 行抛出。但是是的,它是由于第 18 行的执行而抛出的。

另外,我相信您一次使用第 18 行和第 21 行的代码。它们不会在您的代码中同时出现。

import java.util.*;

public class HelloWorld{

public static void main(String []args){
    ArrayList<String> al = new ArrayList<String>();

    al.add("a");
    al.add("b");
    al.add("c");
    al.add("d");
    al.add("e");
    al.add("f"); // line 13


    Iterator<String> itr = al.listIterator();
    while(itr.hasNext()){
        al.remove("d"); // line 18
        if(itr.next().equals("d")){  // line 19
            //itr.remove();
            al.remove("d"); // line 21
        }

    }
    System.out.println(al);
}
}

让我们评论第 13 行。所以这基本上是您的代码,带有一些额外的注释。

如果我们检查迭代器的next()的实现,我们会知道它在一开始就检查是否有任何修改(并因此抛出 CoMo) 。之后它只返回元素。

因此,如果我们在第 18 行不使用迭代器从列表中删除元素,则会在接下来的 next() 调用中检测到这一点。但是如果在 next() 方法之后删除元素(在第 21 行;现在第 18 行已注释),则仅在随后的 next() 中才会检测到 CoMo。在您的情况下发生的情况是我们在打印'e'后用完了元素。所以我们从来没有执行过后续的 next(),也没有得到异常。

现在,如果我们取消注释第 13 行(它添加了一个新元素),那么我们将在第 19 行得到 ConcurrentModidificationexception,即执行下一个 next() 时。这证明我们会得到 CoMo Exception,只是我们会在再一次迭代之后得到它。

于 2014-12-29T09:29:04.387 回答
0

正如@VimalBera 在较早的答案中指出的那样:您必须先使用获取下一个元素,itr.next()然后才能在List.

但是,我发现有一些替代方法更适合这个用例。第一个是在删除元素时简单地使用迭代器

public static void main(String[] args) {
    List<String> al = new ArrayList<>();
    al.add("a");
    al.add("b");
    al.add("c");
    al.add("d");
    al.add("e");

    for (Iterator<String> itr = al.listIterator(); itr.hasNext(); ) {
        String s = itr.next();
        if ("d".equals(s)) {
            itr.remove();
        }
    }
    System.out.println(al);
}

请注意,使用for-loop 代替while-loop。这将迭代器的范围缩小到循环本身(这很好,总是尽量保持范围尽可能窄)。

另一种方法是使用Java 8 Streams 尤其是filter方法。但是,这会创建一个全新的列表,并不会真正修改底层列表,但它提供了一个非常好的编程模型。

public static void main(String[] args) {
    List<String> al = Arrays.asList("a", "b", "c", "d", "e");
    List<String> filtered = al.stream()
            .filter(s -> !s.equals("d"))
            .collect(Collectors.toList());
    System.out.println(filtered);
}

使用这种方法,您显然会获得新列表的额外开销,但相反,您可以将列表视为不可变的,这将在多线程环境中很好地工作。

于 2014-12-29T09:46:04.513 回答