【 Java 】拡張For文でremoveするとコケるよ〜って話

## ■ はじめに
現場で拡張For文を使っているリストを操作している処理の中にremove入れたかったのですが、
調べてみるとどうやらコケるらしいので、簡易的な例文で挙動を確認してみました。

:::note warn
備忘録のため誤字脱字等意識せず書いてます。
あらかじめご了承くださいまし。。
:::

## ■ 例文
今回String型を格納するリストを用意しました。
A、B、Cの文字列を格納しており、拡張文の中でAの文字列が格納されているリストは削除する処理を書いています。
```java:① 実行ソース
    public static void main(String[] args) {
		
		List<String> list = new ArrayList<>();
		list.add("A");
		list.add("B");
		list.add("C");
		
		for (String s : list) {
			if ("A".equals(s)) {
				list.remove(s);
			}
		}
	}
```
・①を実行するとExceptionが発生して処理がこけてしまいました。
　コンソールに出力された内容は下記になります。　
　これを見たところ、最後の行で「Iterator」の文字列が確認できます。
　Iteratorとはなんぞや状態だったので調べてみたところ、ListとかMapとかのコレクション要素に**繰り返しアクセスする**際に使われるIterableインタフェースのメソッドとのこと。

```java:② コンソール
    Exception in thread "main" java.util.ConcurrentModificationException
	at java.base/java.util.ArrayList$Itr.checkForComodification(ArrayList.java:1013)
	at java.base/java.util.ArrayList$Itr.next(ArrayList.java:967)
	at Iterator.main(Iterator.java:13)
```

・どういう流れかさっぱりなので、Exceptionまでの流れをデバッグで確認してみました。
　最初に、①の拡張For文を書いているところでデバックしてみると、下記ソースが実行されていました。
　拡張For文の処理開始なのでIteratorの作成をしているようです。
　この処理で変数listが、Iteratorとなって返ってきます。
```java:③ Iteratorの作成
    public Iterator<E> iterator() {
        return new Itr();
    }

   /*
    * iterator()戻り値
    * cursor          :0 // 次に返す要素のインデックス
    * expectedModCount:3 // イテレータによりコレクションが変更された回数
    * lastRet         :-1 //最後の要素 なければ-1を返す
    */
```

・Iterator作成後拡張For文に戻りますが、ステップインをすると以下ソースが実行されていました。
　hasNextメソッドは、反復にさらに要素がある場合は true を返すメソッドとのこと。
　ここでは、アクセスしている要素のインデックス(cursor)が要素数(size)と一致していないことを確認していることが確認できます。
　今回実行しているソースでは、インデックス０にアクセスしていて、要素数は３つなため「true」が返ってきます。
```java:④ ArrayList.class.hasNext()
    public boolean hasNext() {
        return cursor != size;
    }
    
    /*
     * hasNext()戻り値
     * true
     */
```
・hasNextの処理後は再度拡張For文に戻ってきます。
　ステップインを行うと次はnextメソッドに処置が移ります。
　nextメソッドは次の要素を取り出すメソッドとのこと。
　そのため、今回は"A"が戻り値として返ってきます。
```java:⑤ ArrayList.class.next()
    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];
    }

    /*
     * next()戻り値
     * "A"
     */
```
　・nextメソッドで"A"の戻り値を受け取った後、①のequalメソッドが実行されtrueが返ってきます。
　次の処理removeメソッドに移ります。
　ステップインすると以下ソースが実行されていました。
　⑦のfastRemoveメソッドで"A"が格納されているリストが削除されています。

```java:⑥ ArrayList.class.remove(Object o)
    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;
    }
```

```java:⑦ ArrayList.class.fastRemove(Object[] es, int i)
    private void fastRemove(Object[] es, int i) {
        // コレクション操作を行うため、modContの値を更新
        modCount++; // 3 → 4　
        final int newSize;
        if ((newSize = size - 1) > i)
            System.arraycopy(es, i + 1, es, i, newSize - i);
        es[size = newSize] = null;
    }
```

・"A"のリストの削除が完了し、次の"B"のリストの処理が始まります。
　先ほど同様、④のhasNextメソッドが実行されます。
　まだ最後まで処理は終わっていないので戻り値はtrueです。
　そして次は⑤ソースのnextメソッドが実行されます。
　ここでcheckForComodificationメソッド内で問題が起きました。

　if文の条件分岐でfalseが返ってきてConcurrentModificationExceptionが発生しました。
　これは、modCount(コレクションが操作された回数)とexpectedModCount(イテレータによりコレクションが変更された回数)を比較した結果、値が一致していなかったためExceptionとなったようです。

```java:⑧ ArrayList.class.checkForComodification()
    final void checkForComodification() {
        if (modCount != expectedModCount)
            throw new ConcurrentModificationException();
    }
```

・では、どうしたら正常に処理が完了できるのか、、、結論Iteratorで用意されているremoveメソッドを使えばexpectedModCountが更新されて正常に処理がされるそう。。
　List操作からIterator操作にソースを修正します。
```java:⑨ 実行ソース　List → Iteratorに書き換え
    public static void main(String[] args) {
		
		List<String> list = new ArrayList<>();
		
		list.add("A");
		list.add("B");
		list.add("C");
		
		// 変数listのIteratorを作成
		Iterator<String> itr = list.iterator();
		
		// 次の要素が存在するか判定
		while (itr.hasNext()) {
			// 次の要素の値を変数strに代入
			String str = itr.next();
			if ("A".equals(str)) {
				// Iteratorクラスのremoveメソッドで削除処理
				itr.remove();
			}
		}
	}
```
　
・実行してみると削除処理は、hasNextメソッドやnextメソッドを使っていたIteratorクラスを実現したitrクラスのremoveメソッドに切り替わっていました。
　fastRemoveメソッドでmodCountが更新された後、removeメソッド内でexpectedModCountに代入しているので差異が発生せず、次回以降Exceptionが発生しないということがわかりました。

```java:⑩ ArrayList.class.remove()
    public void remove() {
        if (lastRet < 0)
            throw new IllegalStateException();
        checkForComodification();

        try {
            // ⑦ fastRemoveメソッドが実行され、modCountの値が4に更新される
            ArrayList.this.remove(lastRet);
            cursor = lastRet;
            lastRet = -1;
            // 変更前：　3 = 4
            // 変更後：　4 = 4
            expectedModCount = modCount;
        } catch (IndexOutOfBoundsException ex) {
            throw new ConcurrentModificationException();
        }
    }
```

## ■ ArraryList.class itrクラス
```java
        private class Itr implements Iterator<E> {
        int cursor;       // index of next element to return
        int lastRet = -1; // index of last element returned; -1 if no such
        int expectedModCount = modCount;

        // prevent creating a synthetic constructor
        Itr() {}

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

        @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];
        }

        public void remove() {
            if (lastRet < 0)
                throw new IllegalStateException();
            checkForComodification();

            try {
                ArrayList.this.remove(lastRet);
                cursor = lastRet;
                lastRet = -1;
                expectedModCount = modCount;
            } catch (IndexOutOfBoundsException ex) {
                throw new ConcurrentModificationException();
            }
        }

        @Override
        public void forEachRemaining(Consumer<? super E> action) {
            Objects.requireNonNull(action);
            final int size = ArrayList.this.size;
            int i = cursor;
            if (i < size) {
                final Object[] es = elementData;
                if (i >= es.length)
                    throw new ConcurrentModificationException();
                for (; i < size && modCount == expectedModCount; i++)
                    action.accept(elementAt(es, i));
                // update once at end to reduce heap write traffic
                cursor = i;
                lastRet = i - 1;
                checkForComodification();
            }
        }

        final void checkForComodification() {
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
        }
```

## ■ 参照
https://nuxtblog.work/blog/rjga-emxt0/