次のようなリストがあるとします:
List<String> list = new ArrayList<>();
list.add("a");
list.add("h");
list.add("f");
list.add("s");
このリストを反復しながら、リストの最後に要素を追加します。しかし、新しく追加された要素を繰り返し処理したくないので、リストの初期サイズまで繰り返し処理します。
for (String s : list)
/* Here I want to add new element if needed while iterating */
誰が私にこれを行うことができますか?
そのためにforeachステートメントを使用することはできません。 foreachは内部的にイテレータを使用しています:
このクラスのiteratorメソッドとlistIteratorメソッドによって返されるイテレーターはフェイルファーストです:イテレーターの作成後、イテレーター自身のremoveまたはaddメソッド以外の方法でリストが構造的に変更された場合、イテレーターはConcurrentModificationExceptionをスローします。
(ArrayList javadocから)
Foreachステートメントでは、イテレータのaddメソッドにアクセスできません。いずれにしても、末尾に追加されないため、目的のaddタイプではありません。リストを手動で走査する必要があります。
int listSize = list.size();
for(int i = 0; i < listSize; ++i)
list.add("whatever");
これは、ランダムアクセスを許可するリストに対してのみ効率的であることに注意してください。リストにRandomAccessマーカーインターフェイスが実装されているかどうかを確認することで、この機能を確認できます。 ArrayListにはランダムアクセスがあります。リンクリストにはありません。
リストのコピーを反復処理し、元のリストに新しい要素を追加します。
for (String s : new ArrayList<String>(list))
{
list.add("u");
}
リストのタイプであるArrayListオブジェクトのコピーを作成する方法 を参照してください
明示的なインデックス処理が必要なため、昔ながらの方法を繰り返します。
List myList = ...
...
int length = myList.size();
for(int i = 0; i < length; i++) {
String s = myList.get(i);
// add items here, if you want to
}
元のリストのコピー(クローン)を反復処理できます。
List<String> copy = new ArrayList<String>(list);
for (String s : copy) {
// And if you have to add an element to the list, add it to the original one:
list.add("some element");
}
ConcurrentModificationException
が生成されるため、リストの反復中にリストに新しい要素を追加することさえできないことに注意してください。
これを行うには、新しい空のtmpリストに要素を追加し、addAll()
を使用してtmpリストを元のリストに追加します。これにより、大きなソースリストを不必要にコピーすることを防ぎます。
OPの元のリストに数百万のアイテムが含まれているとどうなるか想像してみてください。しばらくの間、2倍のメモリを使い果たします。
リソースの節約に加えて、この手法は、80年代スタイルのforループに頼ったり、場合によっては魅力的ではない配列インデックスを効果的に使用したりすることも防ぎます。