7

私はこの奇妙なバグに出くわしました。Collections.sort()同じリストを反復処理するときに、同時変更を検出できるように、ソートされたリストを変更しないようです。コード例:

    List<Integer> my_list = new ArrayList<Integer>();

    my_list.add(2);
    my_list.add(1);

    for (Integer num : my_list) {

        /*
         * print list
         */
        StringBuilder sb = new StringBuilder();
        for (Integer i : my_list)
            sb.append(i).append(",");
        System.out.println("List: " + sb.toString());

        /*
         * sort list
         */
        System.out.println("CurrentElement: " + num);
        Collections.sort(my_list);
    }

出力

List: 2,1,
CurrentElement: 2
List: 1,2,
CurrentElement: 2

を期待するかもしれConcurrentModificationExceptionませんが、それは発生しておらず、コードは機能するはずですが機能します。

4

3 に答える 3

4

反復中にコレクションから要素を追加/削除していないのに、なぜConcurrentModificationExceptionがスローされるのでしょうか。

ConcurrentModificationExceptionは、新しい要素がコレクションに追加された場合、または反復中にコレクションから削除された場合にのみ発生することに注意してください。つまり、コレクションが構造的に変更されている場合です。

(構造上の変更とは、このリストのサイズを変更するもの、または進行中の反復によって誤った結果が生じる可能性があるような方法でリストを混乱させるものです。)

sortはコレクションを構造的に変更するのではなく、順序を変更するだけです。以下のコードは、反復中にコレクションに余分な要素を追加するため、ConcurrentModificationExceptionをスローします。

for(Integer num : my_list) {
    my_list.add(12);
    }

Collectionsクラスのsortメソッドのソースを見ると、ConcurrentModificationExceptionがスローされていません。

この実装は、指定されたリストを配列にダンプし、配列をソートし、リストを反復処理して、配列内の対応する位置から各要素をリセットします。これにより、リンクリストを適切にソートしようとした結果として生じるn2 log(n)のパフォーマンスが回避されます。

public static <T extends Comparable<? super T>> void sort(List<T> list) {
        Object[] a = list.toArray();
        Arrays.sort(a);
        ListIterator<T> i = list.listIterator();
        for (int j=0; j<a.length; j++) {
            i.next();
            i.set((T)a[j]);
        }
    }

本からの抜粋javaGenericsand Collections

セクション11.1で説明されているように、Java 2コレクションのイテレータのポリシーは、バッキングコレクションにアクセスするたびに、構造の変更(一般に、要素が追加または削除されたことを意味します)をチェックします。コレクション)。構造の変更を検出すると、すぐに失敗し、変更されたコレクションの反復を継続して予測できない結果をもたらすのではなく、ConcurrentModificationExceptionをスローします。

于 2012-12-11T23:37:07.310 に答える
1

機能性について言えば、なぜそれがスローされるべきではないのかわかりませんConcurrentModificationException。しかし、ドキュメントによると、イテレータは構造の変更に気付いたときに例外をスローし、構造の変更は次のように定義されます。

構造上の変更とは、リストのサイズを変更したり、進行中の反復で誤った結果が生じるような方法でリストを混乱させたりするものです。

sort要素を再配置するとイテレータが間違った結果をもたらすと主張する議論があると思いますが、定義されたイテレータの正しい結果が何であるかを確認していません。

実装について言えば、なぜそうならないのかは簡単にわかります。ArrayListCollectionsのソースを参照してください。

  • ArrayList.modCountいわゆる構造変更による変化
  • ListItrメソッドはその値のコピーを作成initし、メソッドで変更されていないことを確認します
  • Collections.sortを呼び出すListItr.set呼び出しArratList.set。この最後のメソッドはインクリメントしませんmodCount

したがってListItr.next()、同じように見え、modCount例外はスローされません。

于 2012-12-12T00:10:16.863 に答える
1

Androidの場合、APIのバージョンによって異なります。API 26から、Collections#sort(List<T>, Comparator<? super T>)実際にを呼び出しますList#sort(Comparator<? super E>)。したがって、並べ替えると、別のスレッドでリストを変更したかどうかに応じて、ConcurrentModificationExceptionArrayListが発生する可能性があります。例外をスローするソースコードは次のとおりです。java/util/ArrayList.java

public void sort(Comparator<? super E> c) {
    final int expectedModCount = modCount;
    Arrays.sort((E[]) elementData, 0, size, c);
    if (modCount != expectedModCount) {
        throw new ConcurrentModificationException();
    }
    modCount++;
}
于 2020-07-20T09:19:32.577 に答える