1. ホーム

[解決済み】反復処理中にコレクションから要素を削除する

2022-03-31 12:24:30

質問

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

一方のアプローチを他方より好む理由はありますか(例えば、読みやすさの単純な理由で最初のアプローチを好むなど)?

どのように解決するのですか?

を回避するための代替案をいくつか挙げてみましょう。 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);

これは、行いたい操作が "delete" であると仮定した場合です。

もし、quot;add"したいのであれば、このアプローチも有効ですが、別のコレクションを繰り返し処理し、2番目のコレクションに追加したい要素を決定し、その後、quot; add"を発行すると仮定します。 addAll メソッドを最後に実行します。

ListIteratorの使用

リストを扱う場合、別のテクニックとして ListIterator これは、反復処理中に項目の削除と追加をサポートするものです。

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

繰り返しますが、上記の例では、ご質問のように "remove" メソッドを使用しました。 add メソッドを使用して、反復処理中に新しい要素を追加することができます。

JDK >=8を使用

Java 8またはそれ以上のバージョンで作業する場合、それを利用するための他のテクニックがいくつかあります。

新しい 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 は、あらゆるコレクション(Collection、List、Setなど)で機能します。
  • その ListIterator のテクニックは、明らかにリストに対してのみ有効であり、与えられた ListIterator の実装は、追加と削除の操作をサポートしています。
  • Iterator のアプローチは、どんなタイプのコレクションでも動作しますが、 remove操作しかサポートしていません。
  • を使用すると ListIterator / Iterator のアプローチは、反復しながら削除していくので、何もコピーする必要がないのが明らかな利点です。つまり、これは非常に効率的なのです。
  • JDK 8のストリームの例では、実際には何も削除せず、目的の要素を探してから、元のコレクション参照を新しいものに置き換え、古いものをガベージコレクションするようにしました。つまり、コレクションに対して一度だけ反復処理を行うので、効率的なのです。
  • コレクトと removeAll のアプローチでは、2回繰り返さなければならないのが欠点です。まず、foorループで削除基準に合致するオブジェクトを探し、それが見つかったら、元のコレクションから削除するように要求する。これは、削除するためにこのアイテムを探すという2回目の反復作業を意味する。
  • のremoveメソッドも、そのようなものであることは言うまでもないと思います。 Iterator インターフェースは、Javadocs では "optional" としてマークされています。 Iterator を投げる実装があります。 UnsupportedOperationException remove メソッドを呼び出すと そのため、要素の削除に対するイテレータのサポートが保証されない場合、この方法は他の方法よりも安全性が低いと言えるでしょう。