AFAIK, istnieją dwa podejścia:
- Powtórz kopię kolekcji
- Użyj iteratora rzeczywistej kolekcji
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)?
java
collections
iteration
użytkownik1329572
źródło
źródło
while
mają inne zasady określania zakresu niżfor
fooList
jest zmienną instancji, i wywołujesz metodę podczas pętli, która kończy się wywołaniem innej metody w tej samej klasie, która to robifooList.remove(obj)
. Widziałem, jak to się dzieje. W takim przypadku kopiowanie listy jest najbezpieczniejsze.Odpowiedzi:
Pozwól mi podać kilka przykładów z kilkoma alternatywami, aby uniknąć
ConcurrentModificationException
.Załóżmy, że mamy następujący zbiór książek
Zbieraj i usuwaj
Pierwsza technika polega na zebraniu wszystkich obiektów, które chcemy usunąć (np. Za pomocą rozszerzonej pętli for), a po zakończeniu iteracji usuwamy wszystkie znalezione obiekty.
Zakłada się, że operacja, którą chcesz wykonać, to „usuń”.
Jeśli chcesz „dodać”, to podejście również by działało, ale zakładam, że iterowałbyś inną kolekcję, aby określić, które elementy chcesz dodać do drugiej kolekcji, a następnie wydać
addAll
metodę na końcu.Korzystanie z ListIterator
Jeśli pracujesz z listami, inna technika polega na użyciu narzędzia,
ListIterator
które obsługuje usuwanie i dodawanie elementów podczas samej iteracji.Ponownie użyłem metody „usuń” w powyższym przykładzie, co wydaje się sugerować twoje pytanie, ale możesz również użyć jej
add
metody, aby dodać nowe elementy podczas iteracji.Używanie JDK> = 8
Osoby pracujące z wersją Java 8 lub wyższą mogą skorzystać z kilku innych technik, aby z niej skorzystać.
Możesz użyć nowej
removeIf
metody wCollection
klasie podstawowej:Lub użyj nowego interfejsu API strumienia:
W tym ostatnim przypadku, aby odfiltrować elementy z kolekcji, należy ponownie przypisać oryginalne odwołanie do przefiltrowanej kolekcji (tj.
books = filtered
) Lub użyć przefiltrowanej kolekcji doremoveAll
znalezionych elementów z oryginalnej kolekcji (tjbooks.removeAll(filtered)
.).Użyj listy podrzędnej lub podzbioru
Istnieją również inne alternatywy. Jeśli lista jest posortowana, a chcesz usunąć kolejne elementy, możesz utworzyć listę podrzędną, a następnie ją wyczyścić:
Ponieważ lista podrzędna jest poparta oryginalną listą, byłby to skuteczny sposób na usunięcie tego podkolekcji elementów.
Coś podobnego można osiągnąć za pomocą posortowanych zestawów przy użyciu
NavigableSet.subSet
metody lub dowolnej dostępnej tam metody krojenia.Uwagi:
Wybór metody może zależeć od tego, co zamierzasz zrobić
removeAl
Technika technika działa z dowolną kolekcją (kolekcja, lista, zestaw itp.).ListIterator
technika oczywiście działa tylko z listami, pod warunkiem, że ichListIterator
implementacja oferuje obsługę operacji dodawania i usuwania.Iterator
Podejście będzie działać z każdym rodzajem kolekcji, ale obsługuje tylko operacje usunięcia.ListIterator
/Iterator
podejściu oczywistą zaletą jest to, że nie trzeba niczego kopiować, ponieważ usuwamy je podczas iteracji. Jest to więc bardzo wydajne.removeAll
podejścia wadą jest to, że musimy iterować dwa razy. Najpierw iterujemy w pętli foor w poszukiwaniu obiektu, który spełnia nasze kryteria usuwania, a gdy go znajdziemy, prosimy o usunięcie go z oryginalnej kolekcji, co oznaczałoby drugą pracę iteracji w celu wyszukania tego elementu w celu usunąć to.Iterator
interfejsu jest oznaczona jako „opcjonalna” w Javadocs, co oznacza, że mogą istniećIterator
implementacje, które rzucają,UnsupportedOperationException
jeśli wywołamy metodę remove. Jako taki, powiedziałbym, że to podejście jest mniej bezpieczne niż inne, jeśli nie możemy zagwarantować iteratora wsparcia dla usuwania elementów.źródło
removeAll(filtered)
.removeIf(b -> b.getIsbn().equals(other))
W Javie 8 istnieje inne podejście. Kolekcja # removeIf
na przykład:
źródło
Pierwsze podejście będzie działać, ale ma oczywisty narzut związany z kopiowaniem listy.
Drugie podejście nie zadziała, ponieważ wiele kontenerów nie zezwala na modyfikację podczas iteracji. Obejmuje to
ArrayList
.Jeśli jedyną modyfikacją jest usunięcie bieżącego elementu, możesz sprawić, aby drugie podejście działało, używając
itr.remove()
(to znaczy użyj metody iteratora , aremove()
nie kontenera ). To byłaby moja preferowana metoda dla iteratorów, które obsługująremove()
.źródło
Iterator
interfejsu jest oznaczona jako opcjonalna w Javadocs, co oznacza, że mogą istnieć implementacje Iteratora, które mogą rzucaćUnsupportedOperationException
. Jako taki powiedziałbym, że to podejście jest mniej bezpieczne niż pierwsze. W zależności od implementacji, które mają być zastosowane, pierwsze podejście może być bardziej odpowiednie.remove()
na oryginalnej kolekcji może również wrzucićUnsupportedOperationException
: docs.oracle.com/javase/7/docs/api/java/util/… . Interfejsy kontenerowe Java są niestety zdefiniowane jako wyjątkowo niewiarygodne (szczerze mówiąc, pokonanie punktu interfejsu). Jeśli nie znasz dokładnej implementacji, która będzie używana w środowisku uruchomieniowym, lepiej jest robić rzeczy w niezmienny sposób - np. Użyj interfejsu API strumieni Java 8+ do filtrowania elementów i zebrania ich w nowym kontenerze, a następnie całkowicie zastąp go starym.Tylko drugie podejście będzie działać. Możesz modyfikować kolekcję podczas iteracji, używając
iterator.remove()
tylko. Wszystkie inne próby spowodująConcurrentModificationException
.źródło
Ulubiony Old Timer (nadal działa):
źródło
Nie możesz zrobić drugiego, ponieważ nawet jeśli użyjesz
remove()
metody na Iteratorze , otrzymasz wyjątek .Osobiście wolę pierwszy dla wszystkich
Collection
instancji, pomimo dodatkowego podsłuchu tworzenia nowegoCollection
, uważam, że jest mniej podatny na błędy podczas edycji przez innych programistów. W niektórych implementacjach Collectionremove()
obsługiwany jest Iterator , w innych nie. Możesz przeczytać więcej w dokumentacji dla Iteratora .Trzecią alternatywą jest utworzenie nowego
Collection
, powtórzenie w stosunku do oryginału i dodanie wszystkich członków pierwszegoCollection
do drugiegoCollection
, których nie można usunąć. W zależności od wielkościCollection
i liczby usunięć może to znacznie zaoszczędzić na pamięci w porównaniu z pierwszym podejściem.źródło
Wybrałbym drugi, ponieważ nie musisz robić kopii pamięci, a Iterator działa szybciej. Więc oszczędzasz pamięć i czas.
źródło
dlaczego nie to
A jeśli jest to mapa, a nie lista, możesz użyć zestawu kluczy ()
źródło
get(i)
, musisz odwiedzać wszystkie węzły, aż do niego dotrzeszi
.Foo.remove(i);
powrociei--;
?