在Java中失败快速迭代器

时间:2014-12-29 07:11:02

标签: java iterator fail-fast

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之外则抛出异常。解释!

7 个答案:

答案 0 :(得分:2)

这是因为当您使用itr.next()获取下一个元素时,它将check for modification并检查集合的大小是否已更改。那时if (modCount != expectedModCount)条件将成为现实。虽然hasNext()方法仅基于当前光标点返回true或false。

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

答案 1 :(得分:0)

答案 2 :(得分: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。

答案 3 :(得分:0)

就像你正在修改当前由其他人使用的文件(这里是ArrayList)(这里是迭代循环)。

答案 4 :(得分:0)

while(itr.hasNext()){

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

        }
        System.out.println(al);

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

答案 5 :(得分:0)

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

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

此外,我相信您正在使用第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。在您的情况下会发生的是我们在打印&#39; 后耗尽了元素。所以我们从未执行过后续的next(),也没有得到异常。

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

答案 6 :(得分:0)

正如@VimalBera在前面的回答中指出的那样:在itr.next()上调用remove之前,你必须使用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 - 循环而不是while - 循环。这会将迭代器的范围缩小到循环本身(这很好,总是尽量保持范围尽可能窄)。

另一种方法是使用 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);
}

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