282

AFAIK、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()
}

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

4

9 に答える 9

540

を回避するためのいくつかの代替案を含むいくつかの例を挙げましょうConcurrentModificationException

次の本のコレクションがあるとします。

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")));

収集して削除

最初の手法は、削除するすべてのオブジェクトを収集することで構成され(たとえば、拡張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番目のコレクションに追加する要素を決定addAllし、最後にメソッドを発行するとします。

ListIteratorの使用

リストを操作している場合、別の手法はListIterator、反復自体の間にアイテムの削除と追加をサポートするを使用することです。

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

繰り返しになりますが、上記の例では「削除」メソッドを使用しました。これは、質問が示唆しているように見えますが、そのaddメソッドを使用して、反復中に新しい要素を追加することもできます。

JDKの使用>=8

Java 8以降のバージョンを使用している場合は、Java8を利用するために使用できる他のテクニックがいくつかあります。

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

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か(つまり)、フィルター処理されたコレクションをremoveAll元のコレクションから見つかった要素に使用します(つまりbooks.removeAll(filtered))。

サブセットまたはサブセットを使用する

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

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

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

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

考慮事項:

どの方法を使用するかは、意図していることによって異なります。

  • 収集とremoveAl手法は、任意のコレクション(コレクション、リスト、セットなど)で機能します。
  • ListIterator与えられたListIterator実装が追加および削除操作のサポートを提供する場合 、この手法は明らかにリストでのみ機能します。
  • このIteratorアプローチはどのタイプのコレクションでも機能しますが、削除操作のみをサポートします。
  • ListIterator/アプローチを使用Iteratorすると、反復しながら削除するため、何もコピーする必要がないという明らかな利点があります。したがって、これは非常に効率的です。
  • JDK 8ストリームの例では、実際には何も削除されませんが、目的の要素が検索され、元のコレクション参照が新しいものに置き換えられ、古いコレクションがガベージコレクションされます。したがって、コレクションを1回だけ繰り返すと、効率的です。
  • 収集とremoveAllアプローチの欠点は、2回反復する必要があることです。最初に、削除基準に一致するオブジェクトを探してfoor-loopを繰り返し、それを見つけたら、元のコレクションから削除するように依頼します。これは、次のことを行うために、このアイテムを探すための2回目の反復作業を意味します。それを除く。
  • Iteratorインターフェイスのremoveメソッドは、Javadocで「オプション」としてマークされていることを言及する価値があると思います。つまり、removeメソッドを呼び出すIteratorとスローされる実装が存在する可能性があります。UnsupportedOperationExceptionそのため、要素の削除に対するイテレータのサポートを保証できない場合、このアプローチは他のアプローチよりも安全性が低いと言えます。
于 2012-05-03T13:09:40.717 に答える
22

Old Timer Favorite(それはまだ機能します):

List<String> list;

for(int i = list.size() - 1; i >= 0; --i) 
{
        if(list.get(i).contains("bad"))
        {
                list.remove(i);
        }
}

利点:

  1. リストを1回だけ繰り返します
  2. 余分なオブジェクトが作成されたり、その他の不要な複雑さが発生したりすることはありません
  3. 削除されたアイテムのインデックスを使用しようとしても問題はありません。なぜなら...まあ、考えてみてください!
于 2020-03-26T00:45:47.107 に答える
18

Java 8には、別のアプローチがあります。Collection#removeIf

例えば:

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

list.removeIf(i -> i > 2);
于 2017-10-04T07:19:48.933 に答える
15

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

最初のアプローチは機能しますが、リストをコピーするという明らかなオーバーヘッドがあります。

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

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

于 2012-05-03T13:07:22.387 に答える
5

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

于 2012-05-03T13:07:22.640 に答える
1

Iteratorremove()メソッドを使用しても、例外がスローされるため、2番目の方法は実行できません。

個人的にはCollection、新しいものを作成することをさらに耳にしたにもかかわらず、Collection他の開発者による編集中にエラーが発生する可能性が低いと思いますが、すべてのインスタンスで最初のものを好みます。一部のコレクションの実装では、イテレータremove()がサポートされていますが、サポートされていない場合もあります。Iteratorのドキュメントで詳細を読むことができます。

3番目の方法は、新しいものを作成Collectionし、元のメンバーを繰り返し処理して、削除の対象ではないCollection最初のメンバーを2番目のメンバーに追加することです。のサイズと削除の数によっては、最初のアプローチと比較した場合、これによりメモリを大幅に節約できます。CollectionCollection

于 2012-05-03T13:12:17.377 に答える
0

メモリのコピーを実行する必要がなく、Iteratorの動作が高速であるため、2番目を選択します。したがって、メモリと時間を節約できます。

于 2012-05-03T13:08:27.513 に答える
0

このサンプルを見ることができます。リストから奇数値を削除すると思われる場合:

public static void main(String[] args) {
    Predicate<Integer> isOdd = v -> v % 2 == 0;
    List<Integer> listArr = Arrays.asList(5, 7, 90, 11, 55, 60);
    listArr = listArr.stream().filter(isOdd).collect(Collectors.toList());
    listArr.forEach(System.out::println);
}
于 2021-12-27T10:51:41.063 に答える
-3

なぜこれではないのですか?

for( int i = 0; i < Foo.size(); i++ )
{
   if( Foo.get(i).equals( some test ) )
   {
      Foo.remove(i);
   }
}

また、リストではなくマップの場合は、keyset()を使用できます。

于 2012-05-03T13:41:17.227 に答える