Usuwanie elementów z kolekcji podczas iteracji

AFAIK, istnieją dwa podejścia:

  1. iteracja nad kopią zbioru
  2. Użyj iteratora rzeczywistego zbioru

Na przykład,

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

I

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

Czy są jakieś powody, aby preferować jedno podejście nad drugim (np. preferowanie pierwszego podejścia z prostej przyczyny czytelności)?

Author: user1329572, 2012-05-03

8 answers

Pozwolę sobie podać kilka przykładów z pewnymi alternatywami, aby uniknąć ConcurrentModificationException.

Załóżmy, że mamy następujący zbiór książek

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

Zbieraj i usuwaj

Pierwsza technika polega na zebraniu wszystkich obiektów, które chcemy usunąć (np. za pomocą rozszerzonej pętli for) i po zakończeniu iteracji usuwamy wszystkie znalezione obiekty.

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

Przypuśćmy, że operacja, którą chcesz wykonać, to "delete".

Jeśli chcesz " dodać" takie podejście również by zadziałało, ale zakładam, że powtórzyłbyś iterację nad inną kolekcją, aby określić, jakie elementy chcesz dodać do drugiej kolekcji, a następnie na końcu wydałeś metodę addAll.

Korzystanie Z Listysterator

Jeśli pracujesz z listami, inna technika polega na użyciu ListIterator, która ma wsparcie dla usuwania i dodawania elementów podczas samej iteracji.

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

Ponownie użyłem metody "Usuń" w powyższym przykładzie, która twoje pytanie wydawało się sugerować, ale możesz również użyć jego metody add, aby dodać nowe elementy podczas iteracji.

Korzystanie z JDK 8

Dla osób pracujących z Javą 8 lub wersjami nadrzędnymi, istnieje kilka innych technik, których możesz użyć, aby je wykorzystać.

Możesz użyć nowej metody removeIf w klasie bazowej Collection:

ISBN other = new ISBN("0-201-63361-2");
books.removeIf(b -> b.getIsbn().equals(other));
Możesz też użyć nowego API stream:
ISBN other = new ISBN("0-201-63361-2");
List<Book> filtered = books.stream()
                           .filter(b -> b.getIsbn().equals(other))
                           .collect(Collectors.toList());

W tym ostatnim przypadku, aby odfiltrować elementy ze zbioru, przypisujesz oryginalne odniesienie do przefiltrowanego zbioru (tj. books = filtered) lub użyłeś przefiltrowanego zbioru do removeAll znalezionych elementów z oryginalnego zbioru(tj. books.removeAll(filtered)).

Użyj Sublist lub podzbiór

Istnieją również inne alternatywy. Jeśli lista jest posortowana i chcesz usunąć kolejne elementy, możesz utworzyć sublist, a następnie ją wyczyścić:

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

Ponieważ sublist jest wspierany przez oryginalną listę, byłby to skuteczny sposób na usunięcie tej podkolekcji elementów.

Coś podobnego można osiągnąć za pomocą sortowanych zestawów przy użyciu metody NavigableSet.subSet, lub dowolnej z oferowanych tam metod krojenia.

Rozważania:

To, jakiej metody użyjesz, może zależeć od tego, co zamierzasz zrobić

  • technika collect i removeAl Działa z dowolną kolekcją (kolekcją, listą, zestawem itp.).
  • technika ListIterator oczywiście działa tylko z listami, pod warunkiem, że ich podane ListIterator implementacja oferuje wsparcie dla operacji dodawania i usuwania.
  • podejście Iterator będzie działać z każdym typem kolekcji, ale obsługuje tylko operacje usuwania.
  • Z ListIterator/Iterator podejściem oczywistą zaletą jest brak konieczności kopiowania niczego, ponieważ usuwamy podczas iteracji. Więc to jest bardzo wydajne.
  • przykład strumieni JDK 8 właściwie niczego nie usuwał, ale szukał pożądanych elementów, a następnie zastąpiliśmy oryginalny odnośnik do kolekcji przez Nowy, a stary niech będzie śmieciem. Tak więc iterujemy tylko raz nad zbiorem i byłoby to skuteczne.
  • w collect i removeAll podejście wadą jest to, że musimy iterację dwa razy. Najpierw wykonujemy iterację w foor-loop, szukając obiektu spełniającego nasze kryteria usuwania, a gdy już go znajdziemy, prosimy o usunięcie go z oryginalnej kolekcji, co oznaczałoby drugą iterację szukania tego przedmiotu w celu usunięcia go.
  • I myślę, że warto wspomnieć, że metoda remove interfejsu Iterator jest oznaczona jako "opcjonalna" w Javadocs, co oznacza, że mogą istnieć implementacje Iterator, które rzucają UnsupportedOperationException, jeśli wywołamy metodę remove. Jako takie, powiedziałbym, że takie podejście jest mniej bezpieczne niż inne, jeśli nie możemy zagwarantować wsparcia iteratora dla usuwania elementów.
 244
Author: Edwin Dalorzo,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-04-25 13:34:25

Czy są jakieś powody, aby preferować jedno podejście nad drugim

Pierwsze podejście będzie działać, ale ma oczywisty narzut kopiowania listy.

Drugie podejście nie zadziała, ponieważ wiele kontenerów nie pozwala na modyfikację podczas iteracji. obejmuje to ArrayList.

Jeśli jedyną modyfikacją jest usunięcie bieżącego elementu, możesz sprawić, że drugie podejście zadziała za pomocą itr.remove() (to znaczy, użyj iteratora 's remove() metoda, Nie Pojemnik's). jest to moja preferowana metoda dla iteratorów obsługujących remove().

 11
Author: NPE,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:10:34

W Javie 8 istnieje inne podejście. Kolekcja # removeIf

Eg:

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

list.removeIf(i -> i > 2);
 9
Author: Santhosh,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-10-04 07:19:48

Tylko drugie podejście zadziała. Możesz modyfikować kolekcję podczas iteracji używając tylko iterator.remove(). Wszystkie inne próby spowodują ConcurrentModificationException.

 4
Author: AlexR,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-05-03 13:07:22

Nie możesz zrobić drugiej, ponieważ nawet jeśli użyjesz metody remove() Na Iteratorze , otrzymasz wyjątek.

Osobiście wolałbym pierwsze ze wszystkich Collection instancje, pomimo dodatkowego podsłuchu tworzenia nowego Collection, uważam, że jest mniej podatny na błędy podczas edycji przez innych deweloperów. W niektórych implementacjach kolekcji, Iterator remove() jest obsługiwany, w innych nie. więcej informacji można znaleźć w dokumentacji Iterator .

Trzecią alternatywą jest utworzenie nowego Collection, iteracja nad oryginałem i dodanie wszystkich członków pierwszego Collection do drugiego Collection, które są , a nie do usunięcia. W zależności od rozmiaru Collection i liczby usunięć, może to znacznie zaoszczędzić na pamięci, w porównaniu z pierwszym podejściem.

 1
Author: Jon,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-05-03 13:42:22

Wybrałbym drugą, ponieważ nie trzeba robić kopii pamięci, a Iterator działa szybciej. Oszczędzasz pamięć i czas.

 0
Author: Calin Andrei,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-05-03 13:08:27

Istnieje również proste rozwiązanie do "iteracji" a Collection i usuwania każdego elementu.

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

Po prostu łączy się z pętlą, dopóki lista nie będzie pusta, a przy każdej iteracji usuwamy pierwszy element za pomocą remove(0).

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

Nie wydaje mi się, aby to miało jakąś poprawę w porównaniu do Iterator, nadal wymagało zmiany listy, ale podoba mi się prostota tego rozwiązania.

 0
Author: AxelH,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-05-02 11:32:06

Dlaczego nie to?

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

I jeśli jest to mapa, a nie lista, możesz użyć keyset ()

 -1
Author: Drake Clarris,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-05-03 13:41:17