web-dev-qa-db-ja.com

繰り返し処理中にコレクションから要素を削除する

私の知る限りでは、2つのアプローチがあります。

  1. コレクションのコピーを繰り返します
  2. 実際のコレクションのイテレータを使う

例えば、

List<Foo> fooListCopy = new ArrayList<Foo>(fooList);
for(Foo foo : fooListCopy){
    // modify actual fooList
}

そして

Iterator<Foo> itr = fooList.iterator();
while(itr.hasNext()){
    // modify actual fooList using itr.remove()
}

あるアプローチを他のアプローチよりも優先する理由はありますか(たとえば、読みやすさの単純な理由から最初のアプローチを優先する)。

170
user1329572

ConcurrentModificationExceptionname__を回避するためのいくつかの選択肢をいくつか例に挙げましょう。

次のような本があるとします。

List<Book> books = new ArrayList<Book>();
books.add(new Book(new ISBN("0-201-63361-2")));
books.add(new Book(new ISBN("0-201-63361-3")));
books.add(new Book(new ISBN("0-201-63361-4")));

収集して削除

第1の技術は、(例えば、強化されたforループを使用して)削除したい全てのオブジェクトを集めることにあり、そして我々が繰り返しを終えた後、我々は全ての見つけられたオブジェクトを削除する。

ISBN isbn = new ISBN("0-201-63361-2");
List<Book> found = new ArrayList<Book>();
for(Book book : books){
    if(book.getIsbn().equals(isbn)){
        found.add(book);
    }
}
books.removeAll(found);

これはあなたがしたい操作が "削除"であることを想定しています。

「追加」したい場合はこの方法でもうまくいきますが、別のコレクションを反復して2番目のコレクションに追加する要素を決定し、最後にaddAllname__メソッドを発行することをお勧めします。

ListIteratorを使用

あなたがリストを使って作業しているならば、もう一つのテクニックは反復それ自身の間にアイテムの削除と追加をサポートするListIteratorname__を使うことにあります。

ListIterator<Book> iter = books.listIterator();
while(iter.hasNext()){
    if(iter.next().getIsbn().equals(isbn)){
        iter.remove();
    }
}

繰り返しますが、上記の例では "remove"メソッドを使用しましたが、これはあなたの質問が暗示しているように思われるものですが、繰り返しの間に新しい要素を追加するためにaddname__メソッドを使用することもできます。

JDK> = 8を使用

Java 8またはそれ以降のバージョンを扱う人のために、あなたがそれを利用するために使うことができる他のいくつかのテクニックがあります。

removeIfname__基本クラスで新しいCollectionname__メソッドを使用できます。

ISBN other = new ISBN("0-201-63361-2");
books.removeIf(b -> b.getIsbn().equals(other));

または、新しいストリームAPIを使用してください。

ISBN other = new ISBN("0-201-63361-2");
List<Book> filtered = books.stream()
                           .filter(b -> b.getIsbn().equals(other))
                           .collect(Collectors.toList());

この最後のケースでは、コレクションから要素を除外するために、フィルタされたコレクションへの元の参照を再割り当てするか(つまりbooks = filtered)、元のコレクションから見つかった要素をremoveAllname__に割り当てます(つまりbooks.removeAll(filtered))。

サブリストまたはサブセットを使用

他の選択肢もあります。リストがソートされていて、連続した要素を削除したい場合は、サブリストを作成してそれをクリアすることができます。

books.subList(0,5).clear();

サブリストは元のリストに基づいているので、これは要素のこのサブコレクションを削除するための効率的な方法になります。

NavigableSet.subSetメソッド、またはそこで提供されているスライス方法のいずれかを使用したソートセットでも、同様のことが実現できます。

考慮事項

どの方法を使用するかは、あなたが何をしようとしているかによって異なります。

  • CollectとremoveAlname__のテクニックはどのCollection(Collection、List、Setなど)でも動作します。
  • 指定されたListIteratorname__実装が追加および削除操作のサポートを提供するのであれば、ListIteratorname__技法は明らかにリストに対してのみ機能します。
  • Iteratorname__アプローチは、あらゆるタイプのコレクションで機能しますが、削除操作のみをサポートします。
  • ListIteratorname __/Iteratorname__アプローチでは、繰り返して削除するので何もコピーする必要がないという明らかな利点があります。だから、これは非常に効率的です。
  • JDK 8ストリームの例では、実際には何も削除されていませんが、目的の要素を探してから、元のコレクション参照を新しいものに置き換え、古い要素をガベージコレクションにします。そのため、コレクションに対して1回だけ反復するので、効率的です。
  • CollectとremoveAllname__のアプローチでの不利な点は、2回繰り返す必要があることです。まず、foor-loopで、削除基準に一致するオブジェクトを探し、それが見つかったら、元のコレクションから削除するように依頼します。それを除く。
  • Iteratorname__インターフェースのremoveメソッドはJavadocでは "optional"としてマークされていることを言及する価値があると思います。つまり、removeメソッドを呼び出すとIteratorname__をスローするUnsupportedOperationExceptionname__実装が存在する可能性があります。そのため、要素の削除に対するイテレータのサポートを保証できない場合、この方法は他の方法よりも安全性が低くなります。
319
Edwin Dalorzo

あるアプローチを他のアプローチよりも優先する理由はありますか

最初の方法はうまくいきますが、リストをコピーすることによる明らかなオーバーヘッドがあります。

多くのコンテナは反復中に変更を許可しないため、2番目のアプローチは機能しません。 これにはArrayListが含まれます

唯一の変更が現在の要素を削除することである場合、 itr.remove() を使用して2番目のアプローチを機能させることができます(つまり、イテレータremove()を使用します)。 コンテナのメソッドではありません。 これはremove()をサポートするイテレータのための私の好ましい方法です。

13
NPE

Java 8では、別のアプローチがあります。 コレクション#removeIf

例えば:

List<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
list.add(3);

list.removeIf(i -> i > 2);
12
Santhosh

2番目のアプローチだけがうまくいくでしょう。 iterator.remove()のみを使用して、反復中にコレクションを変更できます。他のすべての試みはConcurrentModificationExceptionを引き起こします。

5
AlexR

remove()メソッドを IteratorあなたはExceptionがスローされるでしょう で使ったとしても、2番目のことはできません。

個人的には、すべてのCollectionname__インスタンスを最初に作成したほうがよいでしょう。新しいCollectionname__を作成するのにはまだ余計な時間がかかりますが、他の開発者による編集中にエラーが発生しにくいと思います。一部のCollection実装では、イテレータremove()がサポートされていますが、それ以外はサポートされていません。 イテレータ のドキュメントでもっと読むことができます。

3番目の方法は、新しいCollectionname__を作成し、元のものを反復処理して、最初のCollectionname__のすべてのメンバーを2番目のCollectionname__に追加することですnot削除します。 Collectionname__のサイズと削除の数によっては、最初の方法と比較すると、これによってメモリを大幅に節約できます。

1
Jon

あなたがメモリのコピーをする必要がなくて、そしてイテレータがより速く働くので、私は2番目を選びます。だからあなたはメモリと時間を節約できます。

0
Calin Andrei

Collectionを「繰り返し」て各項目を削除する簡単な方法もあります。

List<String> list = new ArrayList<>();
//Fill the list

それは単にリストが空になるまでループすることで単純化され、そしてそれぞれの繰り返しで、remove(0)で最初の要素を削除します。

while(!list.isEmpty()){
    String s = list.remove(0);
    // do you thing
}

私はこれがIteratorと比較して改善があるとは思わない、それはまだ可変リストを持っている必要がありますが、私はこの解決法の単純さが好きです。

0
AxelH