Mając listę elementów, chcę pobrać element o danej właściwości i usunąć go z listy. Najlepsze rozwiązanie jakie znalazłem to:
ProducerDTO p = producersProcedureActive
.stream()
.filter(producer -> producer.getPod().equals(pod))
.findFirst()
.get();
producersProcedureActive.remove(p);
Czy można łączyć get i remove w wyrażeniu lambda?
java
lambda
java-8
java-stream
Marco Stramezzi
źródło
źródło
get()
tutaj! Nie masz pojęcia, czy jest pusty, czy nie. Rzucisz wyjątek, jeśli elementu tam nie było. Zamiast tego użyj jednej z bezpiecznych metod, takich jak ifPresent, orElse, orElseGet lub orElseThrow.list
dla którychPredicate
jest prawdziwe, czy tylko pierwszy (z prawdopodobnie zera, jednego lub wielu elementów)?Odpowiedzi:
Aby usunąć element z listy
na przykład:
objectA.removeIf(x -> blockedWorkerIds.contains(x)); List<String> str1 = new ArrayList<String>(); str1.add("A"); str1.add("B"); str1.add("C"); str1.add("D"); List<String> str2 = new ArrayList<String>(); str2.add("D"); str2.add("E"); str1.removeIf(x -> str2.contains(x)); str1.forEach(System.out::println);
WYJŚCIE: A B C
źródło
removeIf
jest eleganckim rozwiązaniem do usuwania elementów z kolekcji, ale nie zwraca usuniętego elementu.Chociaż wątek jest dość stary, nadal uważa się, że zapewnia rozwiązanie - użycie
Java8
.Skorzystaj z
removeIf
funkcji. Czas jest złożonyO(n)
Dokumentacja API: removeIf docs
Założenie:
producersProcedureActive
jestList
UWAGA: Przy takim podejściu nie będziesz w stanie przechwycić usuniętego elementu.
źródło
Rozważ użycie podstawowych iteratorów Java do wykonania zadania:
public static <T> T findAndRemoveFirst(Iterable<? extends T> collection, Predicate<? super T> test) { T value = null; for (Iterator<? extends T> it = collection.iterator(); it.hasNext();) if (test.test(value = it.next())) { it.remove(); return value; } return null; }
Zalety :
Iterable
nawet bezstream()
wsparcia (przynajmniej te implementująceremove()
na swoim iteratorze) .Wady :
Jeśli chodzi o
inne odpowiedzi jasno pokazują, że jest to możliwe, ale powinieneś być tego świadomy
ConcurrentModificationException
może zostać wyrzucony podczas usuwania elementu z iterowanej listyźródło
remove()
metody, które rzucają UOE. (Oczywiście nie te dla kolekcji JDK, ale myślę, że niesprawiedliwe jest powiedzenie „działa na dowolnych Iterowalnych”).default
implementacjaremoveIf
przyjmuje takie samo założenie, ale oczywiście jest zdefiniowanaCollection
raczej niżIterable
…Bezpośrednim rozwiązaniem byłoby wywołanie
ifPresent(consumer)
opcjonalnego zwróconego przezfindFirst()
. Ten konsument zostanie wywołany, gdy opcja nie jest pusta. Zaletą jest również to, że nie zgłosi wyjątku, jeśli operacja find zwróci pusty element opcjonalny, tak jak zrobiłby to twój obecny kod; zamiast tego nic się nie stanie.Jeśli chcesz zwrócić wartość usunięta, możesz do wyniku powołania :
map
Optional
remove
producersProcedureActive.stream() .filter(producer -> producer.getPod().equals(pod)) .findFirst() .map(p -> { producersProcedureActive.remove(p); return p; });
Pamiętaj jednak, że
remove(Object)
operacja ponownie przeszuka listę, aby znaleźć element do usunięcia. Jeśli masz listę z dostępem losowym, taką jak anArrayList
, lepiej byłoby utworzyć strumień na indeksach listy i znaleźć pierwszy indeks pasujący do predykatu:IntStream.range(0, producersProcedureActive.size()) .filter(i -> producersProcedureActive.get(i).getPod().equals(pod)) .boxed() .findFirst() .map(i -> producersProcedureActive.remove((int) i));
Dzięki temu rozwiązaniu
remove(int)
operacja działa bezpośrednio na indeksie.źródło
LinkedList
nie powinieneś używać API strumienia, ponieważ nie ma rozwiązania bez przejścia co najmniej dwa razy. Ale nie znam żadnego scenariusza z prawdziwego życia, w którym akademicka przewaga połączonej listy mogłaby zrekompensować jej rzeczywisty narzut. Więc prostym rozwiązaniem jest nigdy nie używaćLinkedList
.remove(Object)
zwraca tylkoboolean
informację, czy istnieje element do usunięcia, czy nie.boxed()
ciebie możesz uzyskaćOptionalInt
tylkomap
odint
doint
. W przeciwieństwie do tegoIntStream
nie mamapToObj
metody. Zboxed()
, dostaniesz,Optional<Integer>
który pozwala namap
dowolny obiekt, tj.ProducerDTO
Zwrócony przezremove(int)
. Rzut odInteger
doint
jest niezbędny do ujednoznacznienia międzyremove(int)
aremove(Object)
.Użyj filtru Java 8 i utwórz kolejną listę, jeśli nie chcesz zmieniać starej listy:
źródło
Jestem pewien, że będzie to niepopularna odpowiedź, ale działa ...
ProducerDTO[] p = new ProducerDTO[1]; producersProcedureActive .stream() .filter(producer -> producer.getPod().equals(pod)) .findFirst() .ifPresent(producer -> {producersProcedureActive.remove(producer); p[0] = producer;}
p[0]
albo będzie zawierał znaleziony element, albo będzie pusty.„Sztuczka” polega tutaj na obejściu problemu „efektywnie ostatecznego” przez użycie odwołania do tablicy, które jest faktycznie ostateczne, ale ustawia swój pierwszy element.
źródło
.orElse(null)
aby odebraćProducerDTO
lubnull
….orElse(null)
i miećif
, nie?remove()
również za pomocąorElse(null)
?if(p!=null) producersProcedureActive.remove(p);
to wciąż krócej niż wyrażenie lambda wifPresent
wywołaniu.Z Eclipse Kolekcje można używać
detectIndex
razem zremove(int)
na dowolnym java.util.List.List<Integer> integers = Lists.mutable.with(1, 2, 3, 4, 5); int index = Iterate.detectIndex(integers, i -> i > 2); if (index > -1) { integers.remove(index); } Assert.assertEquals(Lists.mutable.with(1, 2, 4, 5), integers);
Jeśli używasz
MutableList
typu z kolekcji Eclipse, możesz wywołaćdetectIndex
metodę bezpośrednio z listy.MutableList<Integer> integers = Lists.mutable.with(1, 2, 3, 4, 5); int index = integers.detectIndex(i -> i > 2); if (index > -1) { integers.remove(index); } Assert.assertEquals(Lists.mutable.with(1, 2, 4, 5), integers);
Uwaga: jestem promotorem Eclipse Collections
źródło
Kiedy chcemy przenieść wiele elementów z listy do nowej listy (filtrować za pomocą predykatu) i usunąć je z istniejącej listy , nigdzie nie mogłem znaleźć poprawnej odpowiedzi.
Oto, jak możemy to zrobić za pomocą partycjonowania Java Streaming API.
Map<Boolean, List<ProducerDTO>> classifiedElements = producersProcedureActive .stream() .collect(Collectors.partitioningBy(producer -> producer.getPod().equals(pod))); // get two new lists List<ProducerDTO> matching = classifiedElements.get(true); List<ProducerDTO> nonMatching = classifiedElements.get(false); // OR get non-matching elements to the existing list producersProcedureActive = classifiedElements.get(false);
W ten sposób skutecznie usuniesz przefiltrowane elementy z oryginalnej listy i dodasz je do nowej listy.
Patrz 5.2. Kolektory. Partycjonowanie Według sekcji tego artykułu .
źródło
Jak sugerowali inni, może to być przypadek użycia dla pętli i elementów iteracyjnych. Moim zdaniem to najprostsze podejście. Jeśli chcesz zmodyfikować listę w miejscu, i tak nie można jej uznać za „rzeczywiste” programowanie funkcjonalne. Ale możesz użyć
Collectors.partitioningBy()
, aby uzyskać nową listę z elementami, które spełniają twój warunek, i nową listę tych, które nie spełniają. Oczywiście przy takim podejściu, jeśli masz wiele elementów spełniających warunek, wszystkie będą na tej liście, a nie tylko na pierwszym.źródło
Poniższa logika jest rozwiązaniem bez modyfikowania oryginalnej listy
List<String> str1 = new ArrayList<String>(); str1.add("A"); str1.add("B"); str1.add("C"); str1.add("D"); List<String> str2 = new ArrayList<String>(); str2.add("D"); str2.add("E"); List<String> str3 = str1.stream() .filter(item -> !str2.contains(item)) .collect(Collectors.toList()); str1 // ["A", "B", "C", "D"] str2 // ["D", "E"] str3 // ["A", "B", "C"]
źródło
Łącząc mój wstępny pomysł i Twoje odpowiedzi, doszedłem do tego, co wydaje się być odpowiedzią na moje własne pytanie:
public ProducerDTO findAndRemove(String pod) { ProducerDTO p = null; try { p = IntStream.range(0, producersProcedureActive.size()) .filter(i -> producersProcedureActive.get(i).getPod().equals(pod)) .boxed() .findFirst() .map(i -> producersProcedureActive.remove((int)i)) .get(); logger.debug(p); } catch (NoSuchElementException e) { logger.error("No producer found with POD [" + pod + "]"); } return p; }
Pozwala usunąć obiekt za pomocą
remove(int)
tego, że nie przechodzi ponownie przez listę (zgodnie z sugestią @Tunaki) i pozwala zwrócić usunięty obiekt do obiektu wywołującego funkcję.Czytałem twoje odpowiedzi, które sugerują mi wybór bezpiecznych metod, takich jak
ifPresent
zamiast,get
ale nie znajduję sposobu, aby ich użyć w tym scenariuszu.Czy jest jakaś istotna wada tego rodzaju rozwiązania?
Edytuj następujące porady @Holger
To powinna być funkcja, której potrzebowałem
public ProducerDTO findAndRemove(String pod) { return IntStream.range(0, producersProcedureActive.size()) .filter(i -> producersProcedureActive.get(i).getPod().equals(pod)) .boxed() .findFirst() .map(i -> producersProcedureActive.remove((int)i)) .orElseGet(() -> { logger.error("No producer found with POD [" + pod + "]"); return null; }); }
źródło
get
i łapać wyjątku. To nie tylko zły styl, ale może również powodować złe wyniki. Czyste rozwiązanie jest jeszcze prostsze,return /* stream operation*/.findFirst() .map(i -> producersProcedureActive.remove((int)i)) .orElseGet(() -> { logger.error("No producer found with POD [" + pod + "]"); return null; });
zadaniem jest: pobrać ✶ i ✶ usunąć element z listy
p.stream().collect( Collectors.collectingAndThen( Collector.of( ArrayDeque::new, (a, producer) -> { if( producer.getPod().equals( pod ) ) a.addLast( producer ); }, (a1, a2) -> { return( a1 ); }, rslt -> rslt.pollFirst() ), (e) -> { if( e != null ) p.remove( e ); // remove return( e ); // get } ) );
źródło