単純なループでループ処理をコレクションから削除しようとすると、この例外が発生します: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);
}
}
}
}
iterator.remove()
の代わりにtableRecords.remove()
を使用する必要があります
反復子からremoveメソッドを使用する場合にのみ、反復するリスト上のアイテムを削除できます。
編集:
イテレータを作成すると、コレクションに適用された変更のカウントが開始されます。イテレータがメソッドを使用せずに(または同じコレクションで別のイテレータを使用して)何らかの変更が行われたことを検出した場合、同じ要素で2回渡されたり、1つをスキップしないことを保証できなくなるため、この例外をスローします
つまり、iterator.removeを介してのみアイテムを削除するようにコードを変更する必要があります(1つのイテレーターのみを使用)。
OR
削除するアイテムのリストを作成し、反復の終了後に削除します。
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 Iterator
sは、基になるコンテナがIterator
を使用して変更されずに変更されるたびに、「高速で失敗する」ことを目的としています。ネストされたイテレータを使用しているため、一方に発行されたremove()
操作は、使用を継続すると、他方にException
をスローさせます。このため、remove()
を発行する必要がある場合は、「外側の」イテレーター(これを実行している)で実行し、その後2番目のイテレーターの使用を中止する必要があります( break
ステートメントが追加されました)。
HashSetのイテレーターの規約では、その特定のイテレーターのremoveメソッドを使用しない限り、ハッシュセットから削除できません。 dependencyIt
の観点からは、remove
メソッドを呼び出す以外の方法でアイテムを削除したため、ConcurrentModificationException
がスローされます。
同じレコードIDを持つ場合、ハッシュセットからレコードを削除したいようです。レコードのequals
メソッドとhashcode
メソッドをオーバーライドして、同じIDのレコードが同じで、同じハッシュコードを持つようにする方が簡単ではないでしょうか? (それが当然のことなら)