web-dev-qa-db-ja.com

イテレータを使用したjava.util.ConcurrentModificationException

単純なループでループ処理をコレクションから削除しようとすると、この例外が発生します:Java.util.ConcurrentModificationException。しかし、私はIteratorを使用していますが、それでもこの例外が生成されます。それを解決する理由と方法はありますか?

HashSet<TableRecord> tableRecords = new HashSet<>();

...

    for (Iterator<TableRecord> iterator = tableRecords.iterator(); iterator.hasNext(); ) {
        TableRecord record = iterator.next();
        if (record.getDependency() == null) {
            for (Iterator<TableRecord> dependencyIt = tableRecords.iterator(); dependencyIt.hasNext(); ) {
                TableRecord dependency = dependencyIt.next(); //Here is the line which throws this exception
                if (dependency.getDependency() != null && dependency.getDependency().getId().equals(record.getId())) {
                    tableRecords.remove(record);
                }
            }
        }
    }
23
user2219247

iterator.remove()の代わりにtableRecords.remove()を使用する必要があります

反復子からremoveメソッドを使用する場合にのみ、反復するリスト上のアイテムを削除できます。

編集:

イテレータを作成すると、コレクションに適用された変更のカウントが開始されます。イテレータがメソッドを使用せずに(または同じコレクションで別のイテレータを使用して)何らかの変更が行われたことを検出した場合、同じ要素で2回渡されたり、1つをスキップしないことを保証できなくなるため、この例外をスローします

つまり、iterator.removeを介してのみアイテムを削除するようにコードを変更する必要があります(1つのイテレーターのみを使用)。

OR

削除するアイテムのリストを作成し、反復の終了後に削除します。

37

Iterator fail-fastプロパティは、次の要素を取得しようとするたびに、基礎となるコレクションの構造の変更をチェックします。変更が見つかった場合、ConcurrentModificationExceptionがスローされます。コレクションクラスのIteratorのすべての実装は、ConcurrentHashMapやCopyOnWriteArrayListなどの並行コレクションクラスを除き、設計上フェイルファーストです。

出典:Google

あなたは以下に書かれた例でよりよく理解するでしょう:-

import Java.util.ArrayList;
import Java.util.Arrays;
import Java.util.Iterator;
import Java.util.List;

public class IteratorExp {
    public static void main(String... q) {
        //CASE - ONE
        List<String> strList = new ArrayList<>(Arrays.asList("a", "b", "c"));
        Iterator<String> itr = strList.iterator();
        /*
         * strList.add("e"); strList.add("f"); strList.add("g");
         */
        while (itr.hasNext()) {
            System.out.println(itr.next());
        }
        /*
         * Exception in thread "main" Java.util.ConcurrentModificationException
         * at Java.util.ArrayList$Itr.checkForComodification(Unknown Source) at
         * Java.util.ArrayList$Itr.next(Unknown Source) at
         * IteratorExp.main(IteratorExp.Java:14)
         */

        //CASE - TWO 
        List<Integer> intList = new ArrayList<>(Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 0));
        Iterator<Integer> itrOne = intList.iterator();
        Iterator<Integer> itrTwo = intList.iterator();
        for (; itrOne.hasNext();) {
            if (itrOne.next().equals(5)) {
                itrOne.remove(); // #1
                //intList.remove(itrOne.next()); // #2
            }
        }
        for (; itrTwo.hasNext();) {
            if (itrTwo.next().equals(5)) {
                itrTwo.remove(); // #1
                //intList.remove(itrTwo.next()); // #2
            }
        }

        /*
         * Exception in thread "main" Java.util.ConcurrentModificationException
         * at Java.util.ArrayList$Itr.checkForComodification(Unknown Source) at
         * Java.util.ArrayList$Itr.next(Unknown Source) at
         * IteratorExp.main(IteratorExp.Java:35)
         */
    }
}

問題は、スコープ内に同時に2つのイテレーターがあり、それらが互いに「戦っている」ことです。この問題を修正する最も簡単な方法は、一致するものが見つかった場合に内側のループから抜け出すことです。

_for (Iterator<TableRecord> iterator = tableRecords.iterator(); iterator.hasNext(); ) {
    TableRecord record = iterator.next();
    if (record.getDependency() == null) {
        for (Iterator<TableRecord> dependencyIt = tableRecords.iterator(); dependencyIt.hasNext(); ) {
            TableRecord dependency = dependencyIt.next(); //Here is the line which throws this exception
            if (dependency.getDependency() != null && dependency.getDependency().getId().equals(record.getId())) {
                iterator.remove();
                break; // ADD THIS LINE
            }
        }
    }
}
_

Java Iteratorsは、基になるコンテナがIteratorを使用して変更されずに変更されるたびに、「高速で失敗する」ことを目的としています。ネストされたイテレータを使用しているため、一方に発行されたremove()操作は、使用を継続すると、他方にExceptionをスローさせます。このため、remove()を発行する必要がある場合は、「外側の」イテレーター(これを実行している)で実行し、その後2番目のイテレーターの使用を中止する必要があります( breakステートメントが追加されました)。

0
sigpwned

HashSetのイテレーターの規約では、その特定のイテレーターのremoveメソッドを使用しない限り、ハッシュセットから削除できません。 dependencyItの観点からは、removeメソッドを呼び出す以外の方法でアイテムを削除したため、ConcurrentModificationExceptionがスローされます。

同じレコードIDを持つ場合、ハッシュセットからレコードを削除したいようです。レコードのequalsメソッドとhashcodeメソッドをオーバーライドして、同じIDのレコードが同じで、同じハッシュコードを持つようにする方が簡単ではないでしょうか? (それが当然のことなら)

0
assylias