Przegląd
W tym krótkim poradniku, będziemy mówić o czterech różnych sposobach usuwania elementów z Kolekcji Java, które pasują do pewnych predykatów.
Definiowanie naszej kolekcji
Najpierw zilustrujemy dwa podejścia, które mutują oryginalną strukturę danych. Następnie porozmawiamy o dwóch innych opcjach, które zamiast usuwać elementy, utworzą kopię oryginalnej Kolekcji bez nich.
W naszych przykładach użyjmy następującej kolekcji, aby zademonstrować, jak możemy osiągnąć ten sam rezultat, używając różnych metod:
Collection<String> names = new ArrayList<>();names.add("John");names.add("Ana");names.add("Mary");names.add("Anthony");names.add("Mark");
Usuwanie elementów za pomocą Iteratora
Iterator w Javie umożliwia nam zarówno chodzenie, jak i usuwanie każdego pojedynczego elementu w Kolekcji.
Aby to zrobić, musimy najpierw pobrać iterator nad jej elementami za pomocą metody iterator. Następnie możemy odwiedzić każdy element za pomocą next i usunąć je za pomocą remove:
Iterator<String> i = names.iterator();while(i.hasNext()) { String e = i.next(); if (e.startsWith("A")) { i.remove(); }}
Pomimo swojej prostoty, istnieją pewne zastrzeżenia, które powinniśmy rozważyć:
- W zależności od kolekcji możemy natknąć się na wyjątki ConcurrentModificationException
- Musimy iterować nad elementami, zanim będziemy mogli je usunąć
- W zależności od kolekcji, remove może zachowywać się inaczej niż oczekiwano. Np: ArrayList.Iterator usuwa element z kolekcji i przesuwa kolejne dane w lewo, podczas gdy, LinkedList.Iterator po prostu dostosowuje wskaźnik do następnego elementu. Jako taki, LinkedList.Iterator wykonuje znacznie lepiej niż ArrayList.Iterator podczas usuwania elementów
Java 8 i Collection.removeIf()
Java 8 wprowadziła nową metodę do interfejsu Collection, która zapewnia bardziej zwięzły sposób usuwania elementów przy użyciu Predicate:
names.removeIf(e -> e.startsWith("A"));
Warto zauważyć, że w przeciwieństwie do podejścia Iteratora, removeIf wykonuje się podobnie dobrze zarówno w LinkedList, jak i ArrayList.
W Javie 8, ArrayList zastępuje domyślną implementację – która opiera się na Iterator – i implementuje inną strategię: najpierw iteruje po elementach i zaznacza te, które pasują do naszego predykatu; następnie iteruje po raz drugi, aby usunąć (i przesunąć) elementy, które zostały zaznaczone w pierwszej iteracji.
Java 8 i wprowadzenie Strumienia
Jedną z nowych głównych funkcji w Javie 8 było dodanie Strumienia (i Kolektorów). Istnieje wiele sposobów na utworzenie Strumienia z jakiegoś źródła. Jednak większość operacji, które wpływają na instancję Strumienia, nie zmutuje jego źródła, a raczej API skupia się na tworzeniu kopii źródła i wykonywaniu na nich dowolnych operacji, których możemy potrzebować.
Przyjrzyjrzyjmy się, jak możemy użyć Strumienia i Kolektorów do znalezienia/filtrowania elementów, które pasują i nie pasują do naszego predykatu.
5.1. Usuwanie elementów za pomocą Stream
Usuwanie, a raczej filtrowanie elementów za pomocą Stream jest dość proste, musimy tylko utworzyć instancję Stream za pomocą naszej Kolekcji, wywołać filtr za pomocą naszego Predykatu, a następnie zebrać wynik za pomocą Kolektorów:
Collection<String> filteredCollection = names .stream() .filter(e -> !e.startsWith("A")) .collect(Collectors.toList());
Streaming jest mniej inwazyjny niż poprzednie podejścia, promuje izolację i umożliwia tworzenie wielu kopii z tego samego źródła. Należy jednak pamiętać, że zwiększa on również ilość pamięci wykorzystywanej przez naszą aplikację.
5.2. Collectors.partitioningBy
Połączenie zarówno Stream.filter, jak i Collectors jest całkiem poręczne, choć możemy natknąć się na scenariusze, w których potrzebujemy zarówno pasujących, jak i niepasujących elementów. W takich przypadkach możemy skorzystać z Collectors.partitioningBy:
Map<Boolean, List<String>> classifiedElements = names .stream() .collect(Collectors.partitioningBy((String e) -> !e.startsWith("A")));String matching = String.join(",", classifiedElements.get(true));String nonMatching = String.join(",", classifiedElements.get(false));
Metoda ta zwraca Mapę, która zawiera tylko dwa klucze, true i false, z których każdy wskazuje na listę zawierającą odpowiednio pasujące i niepasujące elementy.
Podsumowanie
W tym artykule przyjrzeliśmy się niektórym metodom usuwania elementów z Kolekcji i kilku ich zastrzeżeniom.