ArrayList同時修正異常

35408 ワード

まず次のコードを見てください。

public static void main(String[] args) {
		ArrayList<Students> arrayList = new ArrayList<Students>();
		arrayList.add(new Students(" ", 23));
		arrayList.add(new Students(" ", 24));
		arrayList.add(new Students(" ", 24));
		Iterator<Students> iterator = arrayList.iterator();
		while (iterator.hasNext()) {
			Students next = iterator.next();
			if (next.getSname().equals(" ")) {
				arrayList.remove(next);
			}
		}
	}

実行結果は次のとおりです。

Exception in thread "main" java.util.ConcurrentModificationException
	at java.base/java.util.ArrayList$Itr.checkForComodification(ArrayList.java:1009)
	at java.base/java.util.ArrayList$Itr.next(ArrayList.java:963)
	at com.czn.collections.ArrayList.IteratorDemo.main(IteratorDemo.java:15)


探究する


ArrayListの同時修正異常が発生しましたが、なぜこのような異常が発生したのでしょうか?ソースコードを見てみましょう.

(一)add(E e)方法

public boolean add(E e) {
		// 3
		// add 1, add ,
		// modCount 3
        modCount++;
        add(e, elementData, size);
        return true;
    }

(二)arrayList.iterator()メソッド


Iterator iterator = arrayList.iterator();
 public Iterator<E> iterator() {
 		// ArrayList Itr
        return new Itr();
    }

Itrクラスを見てみましょう(まず一部を切り取ります):この部分は主にItrクラスのメンバー変数の初期化です.
private class Itr implements Iterator<E> {
		// : 0
        int cursor;     
        //  , -1
        int lastRet = -1; 
        // 
        int expectedModCount = modCount;
        //Itr 
        Itr() {}

ここで注意すべき点はexpectedModCountも3に割り当てられていることです

(三)iterator.hasNext()メソッド

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

カーソルがarrayListのsize(lengthではないことに注意)に等しくない場合は、まだ最後の位置に着いていないことを示し、trueを返します.そうしないと、最後の位置に着いたことを示し、次の位置がないことを示し、falseを返します.

(四)iterator.next()メソッド

@SuppressWarnings("unchecked")
        public E next() {
            checkForComodification();
            int i = cursor;
            if (i >= size)
                throw new NoSuchElementException();
            Object[] elementData = ArrayList.this.elementData;
            if (i >= elementData.length)
                throw new ConcurrentModificationException();
            cursor = i + 1;
            return (E) elementData[lastRet = i];
        }

ここでは主にcheckForComodification()メソッドに注意する
final void checkForComodification() {
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
        }

実際の修正回数と所望の修正回数が一致しない場合は、同時修正異常を投げ出すmodCountとexpectedModCountはいずれも3に等しいため、まだ異常を投げ出すことはありません

(四)arrayList.remove()メソッド


私たちのコードは3番目の要素に遍歴するとif文ブロックに入ります.
if (next.getSname().equals(" ")) {
	arrayList.remove(next);
}

remove()メソッドのソースコードに入ります.
public boolean remove(Object o) {
    final Object[] es = elementData;
    final int size = this.size;
    int i = 0;
    found: {
        if (o == null) {
            for (; i < size; i++)
                if (es[i] == null)
                    break found;
        } else {
            for (; i < size; i++)
                if (o.equals(es[i]))
                    break found;
        }
        return false;
    }
    fastRemove(es, i);
    return true;
}

// fastRemove() 
private void fastRemove(Object[] es, int i) {
	// 
    modCount++;
    final int newSize;
    if ((newSize = size - 1) > i)
        System.arraycopy(es, i + 1, es, i, newSize - i);
    es[size = newSize] = null;
}

以上のソースコードから分かるようにarrayList.remove()メソッドはmodCountの自己増加(すなわちmodCount==4)も行い、このときarrayListのsizeは2になってwhileサイクルiterator.hasNext()に入る:
public boolean hasNext() {
// cursor 3, size 2, true
 return cursor != size;
 }

次の行コードStudents next = iterator.next();の実行を続行
@SuppressWarnings("unchecked")
public E next() {
    checkForComodification();
    int i = cursor;
    if (i >= size)
        throw new NoSuchElementException();
    Object[] elementData = ArrayList.this.elementData;
    if (i >= elementData.length)
        throw new ConcurrentModificationException();
    cursor = i + 1;
    return (E) elementData[lastRet = i];
}

このメソッドcheckForComodification()のソースコードは、次のように強調されています.
final void checkForComodification() {
//modCount = 4   expectedModCount =3
 if (modCount != expectedModCount)
          throw new ConcurrentModificationException();
  }

ここでは同時修正異常を投げ出しました

特別な場合

ArrayList<Students> arrayList = new ArrayList<Students>();
arrayList.add(new Students(" ", 23));
// 
arrayList.add(new Students(" ", 24));
arrayList.add(new Students(" ", 24));
Iterator<Students> iterator = arrayList.iterator();
while (iterator.hasNext()) {
	Students next = iterator.next();
	if (next.getSname().equals(" ")) {
		arrayList.remove(next);
	}

この場合、2番目の要素が削除されると、削除後に
// while 
public boolean hasNext() {
	//cursor = 2   size = 2, false
    return cursor != size;
}


したがってwhileループは終了し、要素は正常に削除されましたが、同時変更例外は発生しません.理解していない読者は、上記のソースコードに従って一度で理解できるはずですが、2つの状況を比較して理解すると、効果的ですよ.

解決策

ArrayList<Students> arrayList = new ArrayList<Students>();
arrayList.add(new Students(" ", 23));
arrayList.add(new Students(" ", 24));
arrayList.add(new Students(" ", 24));
Iterator<Students> iterator = arrayList.iterator();
while (iterator.hasNext()) {
	Students next = iterator.next();
	if (next.getSname().equals(" ")) {
		iterator.remove();
	}
}

この同時修正異常の問題はiteratorのremove()メソッドを用いて解決できる
// Itr remove() 
 public void remove() {
	if (lastRet < 0)
	     throw new IllegalStateException();
	 checkForComodification();
	
	 try {
	 	// ArrayList remove() 
	     ArrayList.this.remove(lastRet);
	     cursor = lastRet;
	     lastRet = -1;
	     // , 
	     // , 
	     expectedModCount = modCount;
	 } catch (IndexOutOfBoundsException ex) {
	     throw new ConcurrentModificationException();
	 }
}

まとめ


1,集合がaddメソッドを呼び出すたびに,実際の修正回数変数の値は1回ずつ増加する2,反復器を取得するときは,集合は実際の修正集合の回数を所望の修正集合の回数に1回だけ付与する3.集合は、要素を削除する際にも、実際の修正回数の変数に対して自増操作を行う.削除する要素が最後でない場合、反復器の遍歴中にArrayList.remove(E e)メソッド削除要素に同時修正異常は発生しない.利用できるremove()の方法で同時修正異常の問題を解決する