Java のコレクション クラス forEach は、要素を削除するときにエラーを報告します: ConcurrentModificationException

    タイトルが示すように、Java 開発では、コレクションが使い果たされ、コレクション内のすべての要素を削除する必要がある場合があります。それらを走査して、削除操作を順番に呼び出します。最も単純なものは各走査に使用します。

    例は次のとおりです。

public class ListForEachDel {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        list.add("name");
        list.add("id");
        System.out.println(list);
        list.forEach((item) -> {
            // todo
            list.remove(item);
        });
        System.out.println(list);
    }
}

    直接実行してエラーを報告します。

[name, id]
Exception in thread "main" java.util.ConcurrentModificationException
	at java.util.ArrayList.forEach(ArrayList.java:1262)
	at com.xxx.coll.ListForEachDel.main(ListForEachDel.java:22)

    この問題については、エラー スタック情報に基づいてソース コードをクリックし、modCount が forEach の ExpectedModCount と等しいと判断されることがわかりました。

    このメソッドが入力されるとすぐに、expectedModCount = modCount が割り当てられます。他の操作がなければ最後に等しいはずですが、ここには modCount を変更する削除操作があります。

    list.remove(item) を呼び出しました。

 

    fastRemove(index) メソッドは、remove(Object o) メソッドで呼び出されます。

   modCount は fastRemove で変更されるため、expectedModCount と等しくないため、例外がスローされます。

   同様に、add メソッドを使用して要素を追加しても機能しません。つまり、要素を追加および削除するメソッドは、forEach メソッド本体内では使用できません。

    では、forループを使っても大丈夫なのでしょうか?答えは「はい」ですが、それは状況によって異なります。

    1. 添字の動的制約を小さいものから大きいものまで削除します。エラーは報告されませんが、削除は完全ではありません。

    2. 添字の静的制約が小さいものから大きいものへと削除されると、インデックス範囲外エラーが報告されます。

    3. 添え字を大きいものから小さいものまで削除します。すべて削除しても、エラーは報告されません。

   以下のように3つの要素の集合体となっており、削除処理の際に list.size() の動的制約を利用して添え字判定を行った場合、エラーは報告されませんが、要素 2 は最終的に保持されます。静的なlen=list.size()で判定すると要素数が減ると境界を越えてしまいます。

    コードは以下のように表示されます。

List<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
list.add(3);
System.out.println("before : " + list);
int len = list.size();
// i < list.size() 下标判断可以动态改变,不会出现越界问题,但是删不干净
// i < len 会出现下标越界
for (int i = 0; i < list.size(); i++) {
	list.remove(list.get(i));
}
System.out.println("after : " + list);

    エラーは報告されませんが、要素の削除は保持されます。     

    静的添字制約、エラー:

 

    最初の 2 つを削除してもエラーは発生しません。最後の要素になると、添え字が 2 に増加します。実際には、範囲外の要素は 1 つだけです。

   

    下付き文字は大きいものから小さいものへと削除されますが、これは正常です。

    実際、要素の走査と削除には、一般的に iterator イテレータ メソッドを使用することが推奨されます。以下に示すように、通常、イテレータを通じて配列要素を削除するためのコードは次のとおりです。

List<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
list.add(3);
System.out.println("before : " + list);
Iterator iterator = list.iterator();
while(iterator.hasNext()) {
	iterator.next();
	iterator.remove();
}
System.out.println("after : " + list);

    操作結果:

 

    このコードは少し奇妙に見えます。iterator.remove() を呼び出す前に iterator.next() 操作を実行しました。このコードを誤って削除した場合、何が起こるでしょうか?

    これは、Iter.remove() ソース コードでは、各操作の後に lastRet=-1 が変更されるためです。

 

    さらに、イテレータを削除するたびに、expectedModCount=modCount が変更され、forEach 削除のようなエラーが発生しないことがわかります。 

     上で述べたように、Iter.remove() は lastRet を -1 に変更するため、再度削除された場合はエラーが直接報告されます。ソースコードを見て理由を説明します。

    ソース コードの最後のステップでは、添字として lastRet が割り当てられていますが、添字が境界を越えない限り、-1 にはなりません。

    少し言及しますが、ここではイテレータのremove()メソッドを呼び出していますが、コレクションのremove(item)メソッドを呼び出したらどうなるでしょうか?

    最初の削除後に modCount が変更され、次回の削除時に直接エラーが報告されたため、forEach と同じエラーが報告されました。

    特殊な場合があります。つまり、2 つの要素がある場合、エラーは報告されません。

 

    どのような状況ですか?Iter.hasNext() で答えを見つける必要があります。

    ここで削除後、次の操作でカーソル+1したため、この時はたまたま1になっており、セットサイズも2から1に変化したため、イテレータの判定が終わり、エラーを報告する可能性があるコード実行されていないので、ここで言えるのは偶然です。 

    最後に、コレクションの走査と削除の問題を要約しましょう。

    foreach -> 1 エラーを直接報告する ConcurrentModificationException
    iterator -> 1 次の操作ではエラーが報告されません IllegalStateException 
                       2 next がある場合は正常です 
                       3 list.remove(value) を呼び出してもエラーは報告されない可能性がありますが、それでも問題はあります。
    for -> 1 添字の動的制約を小さいものから大きいものまで削除すると、削除が不完全になります。 2 
                       添字の静的な制約を小さいものから大きいものまで削除すると、範囲外エラー IndexOutOfBoundsException が発生します。 
                       3 すべての添え字は、大きいものから小さいものまで削除できます。

おすすめ

転載: blog.csdn.net/feinifi/article/details/131337889