6

最後から 2 番目の要素を削除すると、ConcurrentModificationException はありません

List<String> myList1 = new ArrayList<String>();
Collections.addAll(myList1, "str1","str2","str3","str4","str5");
for(String element : myList1){//no ConcurrentModificationException here
if(element.equalsIgnoreCase("str4"))
    myList1.remove("str4");
}
System.out.println(myList1);

しかし、他の要素を削除すると ConcurrentModificationException が発生します

List<String> myList2 = new ArrayList<String>();
Collections.addAll(myList2, "str1","str2","str3","str4","str5");
for(String element : myList2){//ConcurrentModificationException here
if(element.equalsIgnoreCase("str1"))
    myList2.remove("str1");
}
System.out.println(myList2);

理由は何ですか?

4

4 に答える 4

3

Java は modCount(modification count) と expectedCount を使用して、リストに変更があるかどうかをテストします。

final void checkForComodification() {
    if (modCount != expectedModCount)
        throw new ConcurrentModificationException();
}

どちらの条件でも、削除後の modCount は 6 ですが、予想される ModCount は 5 です。

問題は hasNext() です。

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

リストはカーソルとサイズを使用して、次の要素があるかどうかを確認します。また、checkForComodification() は next() メソッドで呼び出されるため、hasNext() は checkForComodification の前に発生します。

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

    @SuppressWarnings("unchecked")
    public E next() {
        checkForComodification();
        int i = cursor;
        if (i >= size)
            throw new NoSuchElementException();
        Object[] elementData = ArrayList.this.elementData;
        if (i >= elementData.length)
            throw new ConcurrentModificationException();
        cursor = i + 1;
        return (E) elementData[lastRet = i];
    }

したがって、最後から 2 番目の要素を削除すると、cursor=4、size=4 も同様です。hasNext() は false を返します。ループから飛び出し、結果を出力します。

于 2013-02-21T04:03:32.123 に答える
3

私は同じものを見ています、

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class Launcher 
{
    public static void main(String[] args) 
    {
        doThis();
        doThat();
    }

    private static void doThis()
    {
        System.out.println("dothis");
        try
        {
            List<String> myList1 = new ArrayList<String>();
            Collections.addAll(myList1, "str1","str2","str3","str4","str5");
            for(String element : myList1){//no ConcurrentModificationException here
            if(element.equalsIgnoreCase("str4"))
                myList1.remove("str4");
            }
            System.out.println(myList1);
        }
        catch(Exception e)
        {
            e.printStackTrace();
        }
    }

    private static void doThat()
    {
        System.out.println("dothat");
        try
        {
            List<String> myList2 = new ArrayList<String>();
            Collections.addAll(myList2, "str1","str2","str3","str4","str5");
            for(String element : myList2){//ConcurrentModificationException here
            if(element.equalsIgnoreCase("str1"))
                myList2.remove("str1");
            }
            System.out.println(myList2);
        }
        catch(Exception e)
        {
            e.printStackTrace();
        }
    }
}

出力するもの、

dothis
[str1, str2, str3, str5]
dothat
java.util.ConcurrentModificationException
    at java.util.AbstractList$Itr.checkForComodification(Unknown Source)
    at java.util.AbstractList$Itr.next(Unknown Source)
    at com.foo.Launcher.doThat(Launcher.java:41)
    at com.foo.Launcher.main(Launcher.java:12)

そして、私はその理由を見つけました。

于 2013-02-21T04:26:22.713 に答える
2

javac がビルドする実際のコードは次のとおりfor-eachです。

    Iterator<String> i = myList1.iterator();
    while(i.hasNext()) {
        String element = i.next();
        if (element.equalsIgnoreCase("str4"))
            myList1.remove("str4");
    }

これは ArrayList Iterator.hasNext 実装です

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

ご覧hasNext()のとおり、同時変更はチェックされないため、最後から 2 つ目の要素を削除すると、問題に気付かずにループが終了します。

実際には、同時変更next()をチェックするのは奇妙ですが、そうではありません。フェイルファスト イテレータはバグを検出するはずですが、私たちのバグは見過ごされていました。 remove()hasNext()

于 2013-02-21T04:25:01.323 に答える
-2

これは一般的に発生する問題です。StackOverflow には、これをカバーする数百のスレッドがあります。あなたの質問への答えはここにあります:

Java でオブジェクトを変更する際にオブジェクトを反復処理するにはどうすればよいですか?

最後から 2 番目の要素を削除すると、hasNext() チェックが失敗し、ループの反復が停止します。JDK の ArrayList イテレータ コードを確認してください。

http://grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/6-b14/java/util/ArrayList.java#ArrayList.Itr.hasNext%28%29

しかし、2 番目の要素が削除された場合、hasNext() チェックはパスし、最初にチェックするのは arrayList の変更であり、したがって例外である next() メソッドに入ります。このコードを確認してください:

http://grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/6-b14/java/util/ArrayList.java#ArrayList.Itr.next%28%29

最も安全な方法は、反復子 remove メソッドを使用して要素を削除することです。

コードがどのように機能するかをよりよく理解するために、デバッガーでコードをステップ オーバーしてみてください。

于 2013-02-21T04:05:19.740 に答える