Usuwanie elementów z kolekcji podczas iteracji
AFAIK, istnieją dwa podejścia:
- iteracja nad kopią zbioru
- 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)?
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 podaneListIterator
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ć implementacjeIterator
, 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.
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()
.
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);
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
.
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.
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.
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.
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 ()
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