Javaでは、foreachループを使用してコレクションを反復処理するときにコレクションに対してremoveを呼び出すことは正当ですか。例えば:
List<String> names = ....
for (String name : names) {
// Do something
names.remove(name).
}
補遺として、まだ反復されていない項目を削除するのは合法ですか?例えば、
//Assume that the names list as duplicate entries
List<String> names = ....
for (String name : names) {
// Do something
while (names.remove(name));
}
コレクションを反復処理しながら安全にコレクションから削除するには、Iteratorを使用する必要があります。
例えば:
List<String> names = ....
Iterator<String> i = names.iterator();
while (i.hasNext()) {
String s = i.next(); // must be called before you can call i.remove()
// Do something
i.remove();
}
Javaドキュメント :から
このクラスのiteratorメソッドとlistIteratorメソッドによって返される反復子は、非常に高速です。反復子が作成された後、リストが構造的に変更された場合、反復子自身のremoveメソッドまたはaddメソッド以外は、ConcurrentModificationExceptionをスローします。したがって、同時修正に直面しても、イテレータは、将来の未確定の時点で任意の非決定論的な振る舞いをするのではなく、迅速かつ明確に失敗します。
おそらく、多くの初心者にとってはっきりしないのは、for/foreach構文を使ってリストを反復処理すると、必ずアクセスできない反復子が暗黙的に作成されるということです。この情報は見つけることができます ここ
あなたはそうしたくないのです。コレクションによっては未定義の動作を引き起こす可能性があります。 イテレータ を直接使用します。 for each構文は構文上の糖であり、実際には反復子を使用していますが、コードからは隠されているため、 Iterator.remove
にアクセスすることはできません。
このメソッドを呼び出す以外の方法で反復が進行中に基になるコレクションが変更された場合、反復子の動作は不定です。
代わりにあなたのコードを書いてください:
List<String> names = ....
Iterator<String> it = names.iterator();
while (it.hasNext()) {
String name = it.next();
// Do something
it.remove();
}
コードはIterator.remove
ではなくList.remove
を呼び出すことに注意してください。
補遺:
まだ繰り返し処理されていない要素を削除している場合でも、コレクションを変更してからIterator
を使用する必要はありません。これは驚くべき方法でコレクションを変更し、将来のIterator
の操作に影響を与える可能性があります。
「拡張forループ」のJava設計は、反復子をコードに公開しないことでしたが、項目を安全に削除する唯一の方法は、反復子にアクセスすることです。したがって、この場合は、古い学校でやらなければなりません。
for(Iterator<String> i = names.iterator(); i.hasNext();) {
String name = i.next();
//Do Something
i.remove();
}
実際のコードで拡張forループが本当に価値がある場合は、アイテムを一時的なコレクションに追加し、ループの後でリストのremoveAllを呼び出すことができます。
EDIT(re redendum):いいえ、繰り返し処理中にiterator.remove()メソッド以外でリストを変更すると問題が発生します。これを回避する唯一の方法は、CopyOnWriteArrayListを使用することですが、これは実際には並行性の問題を対象としています。
重複を削除するための最も安価な(コード行数の)方法は、リストをLinkedHashSetにダンプすることです(そして必要ならばリストに戻します)。これにより、重複を削除しながら挿入順序が維持されます。
for (String name : new ArrayList<String>(names)) {
// Do something
names.remove(nameToRemove);
}
元のリストから削除する間、リストnames
を複製し、クローンを反復処理します。一番上の答えより少しきれい。
私はイテレータについては知りませんでしたが、ループ内のリストから要素を削除するために今日まで行っていたことは次のとおりです。
List<String> names = ....
for (i=names.size()-1;i>=0;i--) {
// Do something
names.remove(i);
}
これは常に機能しており、他の言語やイテレータをサポートしていない構造体で使用することができます。
はい、for-eachループを使用できます。そのためには、アイテムの削除を保留するための個別のリストを管理し、removeAll()
メソッドを使用して名前リストからそのリストを削除する必要があります。
List<String> names = ....
// introduce a separate list to hold removing items
List<String> toRemove= new ArrayList<String>();
for (String name : names) {
// Do something: perform conditional checks
toRemove.add(name);
}
names.removeAll(toRemove);
// now names list holds expected values
Iterator以外でコレクションからアイテムを安全に削除することはできないと言っていることはまったく正しくありません。ConcurrentHashMapなどの並行コレクションの1つを使用して安全に削除できます。
これがコードの匂いではないことを確認してください。論理を逆にして「排他的」ではなく「包括的」にすることは可能ですか。
List<String> names = ....
List<String> reducedNames = ....
for (String name : names) {
// Do something
if (conditionToIncludeMet)
reducedNames.add(name);
}
return reducedNames;
私をこのページに導いた状況は、リストから要素を削除するためにインデックスを使ってリストをループする古いコードを含んでいました。 foreachスタイルを使うようにリファクタリングしたいと思いました。
ユーザーがアクセスを許可されている要素を確認するために要素のリスト全体をループし、リストから許可されていない要素を削除しました。
List<Service> services = ...
for (int i=0; i<services.size(); i++) {
if (!isServicePermitted(user, services.get(i)))
services.remove(i);
}
これを逆にしてremoveを使わないようにするには:
List<Service> services = ...
List<Service> permittedServices = ...
for (Service service:services) {
if (isServicePermitted(user, service))
permittedServices.add(service);
}
return permittedServices;
いつ「削除」するのが好ましいのでしょうか。考慮すべき点の1つは、大きなリストや高価な「追加」をリストサイズと比較してほんのわずかな数だけ削除することです。非常に多くの追加よりも、少数の削除だけを実行する方が効率的かもしれません。しかし、私の場合、状況はそのような最適化に値しませんでした。
public static void main(String[] args) {
Season.add("Frühling");
Season.add("Sommer");
Season.add("Herbst");
Season.add("WINTER");
for (String s : Season) {
if(!s.equals("Sommer")) {
System.out.println(s);
continue;
}
Season.remove("Frühling");
}
}
リストから要素を削除したいときは、イテレータを使うのが良いでしょう。
removeのソースコードは
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--size] = null;
そのため、リストから要素を削除すると、リストは再構築され、他の要素のインデックスは変更されます。これは、あなたがしたいことをもたらす可能性があります。