web-dev-qa-db-ja.com

この例でJava.util.ConcurrentModificationExceptionが発生しないのはなぜですか?

注:私はIterator#remove()メソッドを知っています。

次のコードサンプルでは、​​mainname__メソッドのList.removeConcurrentModificationExceptionname__をスローする理由を理解していませんが、removename__メソッドではをスローしません

public class RemoveListElementDemo {    
    private static final List<Integer> integerList;

    static {
        integerList = new ArrayList<Integer>();
        integerList.add(1);
        integerList.add(2);
        integerList.add(3);
    }

    public static void remove(Integer toRemove) {
        for(Integer integer : integerList) {
            if(integer.equals(toRemove)) {                
                integerList.remove(integer);
            }
        }
    }

    public static void main(String... args) {                
        remove(Integer.valueOf(2));

        Integer toRemove = Integer.valueOf(3);
        for(Integer integer : integerList) {
            if(integer.equals(toRemove)) {                
                integerList.remove(integer);
            }
        }
    }
}
172
Bhesh Gurung

その理由は次のとおりです。Javadocに記載されているように、

このクラスのiteratorメソッドとlistIteratorメソッドによって返される反復子は、非常に高速です。反復子が作成された後、リストが構造的に変更された場合、反復子自身のremoveメソッドまたはaddメソッド以外は、ConcurrentModificationExceptionをスローします。

このチェックはイテレータのnext()メソッドで行われます(stacktraceからわかるように)。しかし、next()メソッドがtrueを渡した場合にのみhasNext()メソッドに到達します。これは、for eachが境界を満たしているかどうかを確認するために呼び出されるものです。 removeメソッドで、hasNext()が別の要素を返す必要があるかどうかをチェックすると、2つの要素が返されたことがわかります。1つの要素が削除された後のリストには2つの要素しか含まれません。ですから、すべてが桃色であり、繰り返しが終わりました。これは決して呼び出されないnext()メソッドで行われるため、同時変更のチェックは行われません。

次に2番目のループに行きます。 2番目の番号を削除した後、hasNextメソッドはさらに値を返すことができるかどうかをもう一度チェックします。すでに2つの値が返されていますが、リストには1つの値しか含まれていません。しかし、ここでのコードは次のとおりです。

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

1!= 2なので、next()メソッドを続けます。これで、誰かがリストをめちゃくちゃにして例外を起動したことがわかります。

それがあなたの質問を解決することを願っています。

概要

List.remove()は、リストから最後から2番目の要素を削除するときにConcurrentModificationExceptionをスローしません。

257
pushy

該当する場合、それを処理してCollection(Collection自体ではない)のコピーから何かを削除するという方法があります。 Cloneは元のコレクションで、Constructorを介してコピーを作成します。

このような変更が許可されていない場合に、オブジェクトの同時変更を検出したメソッドによって、この例外がスローされることがあります。

あなたの特定のケースでは、最初に、私はfinalがあなたが宣言を過ぎてリストを修正しようとしていることを考えて行く方法であるとは思わない

private static final List<Integer> integerList;

元のリストの代わりにコピーを変更することも検討してください。

List<Integer> copy = new ArrayList<Integer>(integerList);

for(Integer integer : integerList) {
    if(integer.equals(remove)) {                
        copy.remove(integer);
    }
}
42
JAM

アイテムを削除するとき、forward/iteratorメソッドは機能しません。要素をエラーなしで削除できますが、削除されたアイテムにアクセスしようとするとランタイムエラーが発生します。 pushyが示すようにConcurrentModificationExceptionを引き起こすのでイテレータを使用することはできません。代わりに通常のforループを使用してください。

List<Integer> integerList;
integerList = new ArrayList<Integer>();
integerList.add(1);
integerList.add(2);
integerList.add(3);

int size= integerList.size();

//Item to remove
Integer remove = Integer.valueOf(3);

解決策:

リスト要素を削除する場合は、配列を逆の順序でたどります。リストを逆方向に移動するだけで、削除された項目にアクセスすることがなくなり、例外が削除されます。

//To remove items from the list, start from the end and go backwards through the arrayList
//This way if we remove one from the beginning as we go through, then we will avoid getting a runtime error
//for Java.lang.IndexOutOfBoundsException or Java.util.ConcurrentModificationException as when we used the iterator
for (int i=size-1; i> -1; i--) {
    if (integerList.get(i).equals(remove) ) {
        integerList.remove(i);
    }
}
13

このスニペットは常にConcurrentModificationExceptionをスローします。

規則は、「イテレータを使って繰り返し処理している間は変更できません(リストに要素を追加または削除できません)(for-eachループを使用した場合に発生します)」です。

JavaDocs:

このクラスのiteratorメソッドとlistIteratorメソッドによって返されるイテレータは、非常に高速です。イテレータが作成された後、リストが構造的に変更された場合(イテレータ自身のremoveまたはadd以外)メソッドの場合、イテレータはConcurrentModificationExceptionをスローします。

そのため、リスト(または一般的には任意のコレクション)を変更したい場合は、イテレータを使用してください。イテレータは変更を認識しているため、正しく処理されます。

お役に立てれば。

6
Bhushan

私は同じ問題を抱えていましたが、もし私がen要素を繰り返しリストに追加していたのならば。私はこうやった

public static void remove(Integer remove) {
    for(int i=0; i<integerList.size(); i++) {
        //here is maybe fine to deal with integerList.get(i)==null
        if(integerList.get(i).equals(remove)) {                
            integerList.remove(i);
        }
    }
}

リストにイテレータを作成するのではなく、「手動で」イテレータを作成するので、すべてうまくいきます。また、リストのサイズから何かを削除/追加すると、リストの減分/増分の条件にi < integerList.size()の条件で騙されることはありません。

それが助けになれば幸いです、私にとってそれは解決策でした。

5
Gondil

解決するには、反復子for eachfor loopに変更します。

そしてその理由は:

このクラスのiteratorメソッドとlistIteratorメソッドによって返される反復子は、非常に高速です。反復子が作成された後、リストが構造的に変更された場合は、反復子自身のremoveメソッドまたはaddメソッドによる方法を除き.

- 参照されているJavaドキュメント。

1
Stephen

これはJava 1.6でうまく動きます

〜%javac RemoveListElementDemo.Java
〜%Java RemoveListElementDemo
〜%cat RemoveListElementDemo.Java

import Java.util.*;
public class RemoveListElementDemo {    
    private static final List<Integer> integerList;

    static {
        integerList = new ArrayList<Integer>();
        integerList.add(1);
        integerList.add(2);
        integerList.add(3);
    }

    public static void remove(Integer remove) {
        for(Integer integer : integerList) {
            if(integer.equals(remove)) {                
                integerList.remove(integer);
            }
        }
    }

    public static void main(String... args) {                
        remove(Integer.valueOf(2));

        Integer remove = Integer.valueOf(3);
        for(Integer integer : integerList) {
            if(integer.equals(remove)) {                
                integerList.remove(integer);
            }
        }
    }
}

〜%

1
battosai

コピーオンライトコレクションを使用する場合は機能します。ただし、list.iterator()を使用すると、別のスレッドがコレクションを変更した場合でも、返されるIteratorは常に(以下のように)list.iterator()が呼び出されたときと同じように要素のコレクションを参照します。コピーオンライトベースのIteratorまたはListIteratorで呼び出されるすべての変更メソッド(add、set、removeなど)は、UnsupportedOperationExceptionをスローします。

import Java.util.List;
import Java.util.concurrent.CopyOnWriteArrayList;

public class RemoveListElementDemo {    
    private static final List<Integer> integerList;

    static {
        integerList = new CopyOnWriteArrayList<>();
        integerList.add(1);
        integerList.add(2);
        integerList.add(3);
    }

    public static void remove(Integer remove) {
        for(Integer integer : integerList) {
            if(integer.equals(remove)) {                
                integerList.remove(integer);
            }
        }
    }

    public static void main(String... args) {                
        remove(Integer.valueOf(2));

        Integer remove = Integer.valueOf(3);
        for(Integer integer : integerList) {
            if(integer.equals(remove)) {                
                integerList.remove(integer);
            }
        }
    }
}
1
JohnnyO

私の場合、私はこのようにしました:

int cursor = 0;
do {
    if (integer.equals(remove))
        integerList.remove(cursor);
    else cursor++;
} while (cursor != integerList.size());
0
Saif Hamed