web-dev-qa-db-ja.com

Javaでremove in foreachループを呼び出す

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));
}
553
Michael Bobick

コレクションを反復処理しながら安全にコレクションから削除するには、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構文を使ってリストを反復処理すると、必ずアクセスできない反復子が暗黙的に作成されるということです。この情報は見つけることができます ここ

843
Mark

あなたはそうしたくないのです。コレクションによっては未定義の動作を引き起こす可能性があります。 イテレータ を直接使用します。 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の操作に影響を与える可能性があります。

155
Jared Oberhaus

「拡張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にダンプすることです(そして必要ならばリストに戻します)。これにより、重複を削除しながら挿入順序が維持されます。

59
Yishai
for (String name : new ArrayList<String>(names)) {
    // Do something
    names.remove(nameToRemove);
}

元のリストから削除する間、リストnamesを複製し、クローンを反復処理します。一番上の答えより少しきれい。

51
ktamlyn

私はイテレータについては知りませんでしたが、ループ内のリストから要素を削除するために今日まで行っていたことは次のとおりです。

List<String> names = .... 
for (i=names.size()-1;i>=0;i--) {    
    // Do something    
    names.remove(i);
} 

これは常に機能しており、他の言語やイテレータをサポートしていない構造体で使用することができます。

25
Serafeim

はい、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
21

Iterator以外でコレクションからアイテムを安全に削除することはできないと言っていることはまったく正しくありません。ConcurrentHashMapなどの並行コレクションの1つを使用して安全に削除できます。

4
sanity

これがコードの匂いではないことを確認してください。論理を逆にして「排他的」ではなく「包括的」にすることは可能ですか。

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つは、大きなリストや高価な「追加」をリストサイズと比較してほんのわずかな数だけ削除することです。非常に多くの追加よりも、少数の削除だけを実行する方が効率的かもしれません。しかし、私の場合、状況はそのような最適化に値しませんでした。

3
bmcdonald
  1. これを試して2.条件を "WINTER"に変更すると不思議に思うでしょう。
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");
  }
 }
1
Carsten

リストから要素を削除したいときは、イテレータを使うのが良いでしょう。

removeのソースコードは

if (numMoved > 0)
    System.arraycopy(elementData, index+1, elementData, index,
             numMoved);
elementData[--size] = null;

そのため、リストから要素を削除すると、リストは再構築され、他の要素のインデックスは変更されます。これは、あなたがしたいことをもたらす可能性があります。

1
song