Java 8 Lambda Element aus Liste holen und entfernen

88

In einer vorgegebenen Liste von Elementen, mag ich das Element mit einer bestimmten Eigenschaft erhalten und sie aus der Liste entfernen. Die beste Lösung, die ich gefunden habe, ist:

ProducerDTO p = producersProcedureActive
                .stream()
                .filter(producer -> producer.getPod().equals(pod))
                .findFirst()
                .get();
producersProcedureActive.remove(p);

Ist es möglich, get und remove in einem Lambda-Ausdruck zu kombinieren?

Marco Stramezzi
quelle
9
Dies scheint wirklich ein klassischer Fall zu sein, bei dem stattdessen nur eine Schleife und ein Iterator verwendet werden müssen.
Chrylis
1
@chrylis Ich bin nicht einverstanden;) Wir sind so an zwingende Programmierung gewöhnt, dass jeder andere Weg zu exotisch klingt. Stellen Sie sich vor, die Realität wäre umgekehrt: Wir sind sehr an funktionale Programmierung gewöhnt und Java wird ein neues imperatives Paradigma hinzugefügt. Würden Sie sagen, dass dies der klassische Fall für Streams, Prädikate und Optionen ist?
fps
9
Rufen Sie nicht get()hier an! Sie haben keine Ahnung, ob es leer ist oder nicht. Sie werden eine Ausnahme auslösen, wenn das Element nicht vorhanden war. Verwenden Sie stattdessen eine der sicheren Methoden wie ifPresent, orElse, orElseGet oder orElseThrow.
Brian Goetz
@FedericoPeraltaSchaffner Wahrscheinlich Geschmackssache. Ich würde aber auch vorschlagen, nicht beide zu kombinieren. Wenn ich sehe, dass Code mithilfe von Streams einen Wert erhält, gehe ich normalerweise davon aus, dass die Vorgänge im Stream frei von Nebenwirkungen sind. Das Mischen beim Entfernen des Elements kann zu Code führen, der für die Leser irreführend sein kann.
Matthias Wimmer
Nur zur Verdeutlichung: Möchten Sie alle Elemente entfernen, listfür die das Predicatewahr ist, oder nur das erste (von möglicherweise null, einem oder mehreren Elementen)?
Kedar Mhaswade

Antworten:

148

Element aus der Liste entfernen

objectA.removeIf(x -> conditions);

z.B:

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

AUSGABE: A B C.

uma shankar
quelle
Ich würde dies als die beste Antwort vorschlagen
Sergey Pauk
1
Das ist sehr ordentlich. Möglicherweise müssen Sie den equals / hashCode implementieren, wenn dies nicht für Ihre eigenen Objekte erfolgt. (In diesem Beispiel wird ein String verwendet, der sie standardmäßig hat.)
bram000
15
IMHO berücksichtigt die Antwort nicht den Teil "get": removeIfist eine elegante Lösung zum Entfernen von Elementen aus einer Sammlung, gibt jedoch das entfernte Element nicht zurück.
Marco Stramezzi
Es ist ein sehr einfacher Modus, um ein Objekt von Java ArrayList auszuschließen. Thkns viel. Arbeite gut für mich.
Marcelo Rebouças
2
Dies beantwortet die Frage nicht. Die Anforderung besteht darin, das Element aus der Liste zu entfernen und Elemente aus einer neuen Liste zu entfernen.
Shanika Ediriweera
34

Obwohl der Thread ziemlich alt ist, wird immer noch angenommen, dass er eine Lösung bietet Java8.

Nutzen Sie die removeIfFunktion. Zeitliche Komplexität istO(n)

producersProcedureActive.removeIf(producer -> producer.getPod().equals(pod));

API-Referenz: removeIf docs

Annahme: producersProcedureActiveist aList

HINWEIS: Mit diesem Ansatz können Sie das gelöschte Element nicht mehr erfassen.

asifsid88
quelle
Zusätzlich zum Löschen des Elements aus der Liste möchte das OP weiterhin einen Verweis auf das Element.
eee
@eee: Vielen Dank für den Hinweis. Ich habe diesen Teil von OPs ursprünglicher Frage verpasst.
Asifsid88
Nur um dies zu beachten, werden alle Elemente entfernt, die der Bedingung entsprechen. Aber OP scheint notwendig zu sein, nur das erste Element zu entfernen (OP verwendet findFirst ())
nantitv
17

Erwägen Sie die Verwendung von Vanilla Java-Iteratoren, um die Aufgabe auszuführen:

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

Vorteile :

  1. Es ist klar und offensichtlich.
  2. Es wird nur einmal und nur bis zum passenden Element durchlaufen.
  3. Sie können dies Iterableauch ohne stream()Unterstützung tun (zumindest diejenigen, die remove()auf ihrem Iterator implementiert sind ) .

Nachteile :

  1. Sie können dies nicht als einzelner Ausdruck tun (Hilfsmethode oder Variable erforderlich).

Wie für die

Ist es möglich, get und remove in einem Lambda-Ausdruck zu kombinieren?

Andere Antworten zeigen deutlich, dass dies möglich ist, aber Sie sollten sich dessen bewusst sein

  1. Das Suchen und Entfernen kann die Liste zweimal durchlaufen
  2. ConcurrentModificationException kann ausgelöst werden, wenn ein Element aus der Liste entfernt wird, die iteriert wird
Wassili Liaskowski
quelle
4
Ich mag diese Lösung, aber beachten Sie, dass sie einen schwerwiegenden Nachteil hat, den Sie verpasst haben: Viele iterierbare Implementierungen haben remove()Methoden, die UOE auslösen . (Natürlich nicht für JDK-Sammlungen, aber ich denke, es ist unfair zu sagen, dass "auf jedem Iterable funktioniert".)
Brian Goetz
Ich denke, wir könnten davon ausgehen, dass ein Element, das im Allgemeinen entfernt werden
Vasily Liaskovsky,
5
Sie könnten das annehmen, aber wenn Sie sich Hunderte von Iterator-Implementierungen angesehen haben, wäre dies eine schlechte Annahme. (Ich mag den Ansatz immer noch; Sie verkaufen ihn nur zu viel.)
Brian Goetz
2
@ Brian Goetz: Die defaultImplementierung von removeIfmacht die gleiche Annahme, aber natürlich ist es Collectioneher definiert als Iterable...
Holger
13

Die direkte Lösung wäre, die ifPresent(consumer)von zurückgegebene Option aufzurufen findFirst(). Dieser Consumer wird aufgerufen, wenn das optionale nicht leer ist. Der Vorteil ist auch, dass keine Ausnahme ausgelöst wird, wenn die Suchoperation ein leeres optionales Element zurückgibt, wie es Ihr aktueller Code tun würde. Stattdessen wird nichts passieren.

Wenn Sie den entfernten Wert zurückgeben möchten, können Sie mapdas Optionalzum Ergebnis des Aufrufs führen remove:

producersProcedureActive.stream()
                        .filter(producer -> producer.getPod().equals(pod))
                        .findFirst()
                        .map(p -> {
                            producersProcedureActive.remove(p);
                            return p;
                        });

Beachten Sie jedoch, dass die remove(Object)Operation die Liste erneut durchläuft, um das zu entfernende Element zu finden. Wenn Sie eine Liste mit wahlfreiem Zugriff haben, wie z. B. eine ArrayList, ist es besser, einen Stream über die Indizes der Liste zu erstellen und den ersten Index zu finden, der dem Prädikat entspricht:

IntStream.range(0, producersProcedureActive.size())
         .filter(i -> producersProcedureActive.get(i).getPod().equals(pod))
         .boxed()
         .findFirst()
         .map(i -> producersProcedureActive.remove((int) i));

Bei dieser Lösung arbeitet die remove(int)Operation direkt mit dem Index.

Tunaki
quelle
3
Dies ist für eine verknüpfte Liste pathologisch.
Chrylis
1
@chrylis Die Indexlösung wäre in der Tat. Abhängig von der Listenimplementierung würde man einander vorziehen. Eine kleine Bearbeitung vorgenommen.
Tunaki
1
@chrylis: Im Falle eines sollten LinkedListSie die Stream-API möglicherweise nicht verwenden, da es keine Lösung gibt, ohne mindestens zweimal zu durchlaufen. Ich kenne jedoch kein reales Szenario, in dem der akademische Vorteil einer verknüpften Liste den tatsächlichen Aufwand kompensieren kann. Die einfache Lösung ist also, niemals zu verwenden LinkedList.
Holger
2
Oh, so viele Änderungen ... jetzt liefert die erste Lösung das entfernte Element nicht mehr, da remove(Object)nur eine booleanMeldung zurückgegeben wird, ob ein Element entfernt werden musste oder nicht.
Holger
3
@ Marco Stramezzi: Leider wurde der Kommentar, der es erklärt, entfernt. Ohne boxed()bekommt man ein OptionalIntwas nur mapvon intbis kann int. Im Gegensatz dazu IntStreamgibt es keine mapToObjMethode. Mit erhalten boxed()Sie eine, Optional<Integer>die es ermöglicht, mapzu einem beliebigen Objekt, dh dem ProducerDTOvon zurückgegebenen remove(int). Die Besetzung von Integerbis intist notwendig, um zwischen remove(int)und zu unterscheiden remove(Object).
Holger
9

Use kann den Filter von Java 8 verwenden und eine weitere Liste erstellen, wenn Sie die alte Liste nicht ändern möchten:

List<ProducerDTO> result = producersProcedureActive
                            .stream()
                            .filter(producer -> producer.getPod().equals(pod))
                            .collect(Collectors.toList());
Toi Nguyen
quelle
5

Ich bin sicher, dass dies eine unpopuläre Antwort sein wird, aber es funktioniert ...

ProducerDTO[] p = new ProducerDTO[1];
producersProcedureActive
            .stream()
            .filter(producer -> producer.getPod().equals(pod))
            .findFirst()
            .ifPresent(producer -> {producersProcedureActive.remove(producer); p[0] = producer;}

p[0] wird entweder das gefundene Element enthalten oder null sein.

Der "Trick" hier besteht darin, das "effektiv endgültige" Problem zu umgehen, indem eine Array- Referenz verwendet wird, die effektiv endgültig ist, aber ihr erstes Element setzt.

Böhmisch
quelle
1
In diesem Fall ist es nicht so schlimm, aber keine Verbesserung gegenüber der Möglichkeit, einfach anzurufen .orElse(null), um das ProducerDTOoder null...
Holger
In diesem Fall könnte es einfacher sein, nur .orElse(null)ein if, nein?
Tunaki
@Holger aber wie kann man aufrufen remove()zu , indem Sie orElse(null)?
Bohemian
1
Verwenden Sie einfach das Ergebnis. if(p!=null) producersProcedureActive.remove(p);Das ist immer noch kürzer als der Lambda-Ausdruck in Ihrem ifPresentAnruf.
Holger
@holger Ich interpretierte das Ziel der Frage so, dass mehrere Aussagen vermieden wurden - dh eine einzeilige Lösung
Bohemian
4

Mit Eclipse Collections können Sie detectIndexzusammen mit remove(int)jeder java.util.List verwenden.

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

Wenn Sie den MutableListTyp aus Eclipse-Sammlungen verwenden, können Sie die detectIndexMethode direkt in der Liste aufrufen .

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

Hinweis: Ich bin ein Committer für Eclipse-Sammlungen

Donald Raab
quelle
2

Wenn wir wollen mehrere Elemente aus einer Liste in eine neue Liste aufnehmen (mithilfe eines Prädikats filtern) und sie aus der vorhandenen Liste entfernen , konnte ich nirgendwo eine richtige Antwort finden.

Hier erfahren Sie, wie Sie dies mithilfe der Java Streaming API-Partitionierung tun können.

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

Auf diese Weise entfernen Sie die gefilterten Elemente effektiv aus der ursprünglichen Liste und fügen sie einer neuen Liste hinzu.

Siehe 5.2. Collectors.partitioningBy Abschnitt dieses Artikels .

Shanika Ediriweera
quelle
1

Wie andere vorgeschlagen haben, könnte dies ein Anwendungsfall für Schleifen und Iterables sein. Meiner Meinung nach ist dies der einfachste Ansatz. Wenn Sie die Liste direkt ändern möchten, kann sie ohnehin nicht als "echte" Funktionsprogrammierung angesehen werden. Sie können jedoch Collectors.partitioningBy()eine neue Liste mit Elementen erstellen, die Ihrer Bedingung entsprechen, und eine neue Liste mit Elementen, die dies nicht tun. Wenn Sie bei diesem Ansatz mehrere Elemente haben, die die Bedingung erfüllen, befinden sich natürlich alle in dieser Liste und nicht nur die erste.

user140547
quelle
Viel besser, um den Stream zu filtern und Ergebnisse in einer neuen Liste zu sammeln
fps
1

Die folgende Logik ist die Lösung, ohne die ursprüngliche Liste zu ändern

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"]
KimchiMan
quelle
0

Durch die Kombination meiner ursprünglichen Idee und Ihrer Antworten gelangte ich zu der Lösung für meine eigene Frage:

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

Damit können Sie das Objekt entfernen, indem Sie remove(int)die Liste nicht erneut durchlaufen (wie von @Tunaki vorgeschlagen) und das entfernte Objekt an den Funktionsaufrufer zurückgeben.

Ich habe Ihre Antworten gelesen, die mir vorschlagen, sichere Methoden wie ifPresentanstelle von zu wählenget aber ich finde keine Möglichkeit, sie in diesem Szenario zu verwenden.

Gibt es einen wichtigen Nachteil bei dieser Art von Lösung?

Bearbeiten Sie die folgenden @ Holger-Ratschläge

Dies sollte die Funktion sein, die ich brauchte

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; 
            });
}
Marco Stramezzi
quelle
2
Sie sollten getdie Ausnahme nicht verwenden und abfangen. Das ist nicht nur ein schlechter Stil, sondern kann auch zu einer schlechten Leistung führen. Die saubere Lösung ist noch einfacher,return /* stream operation*/.findFirst() .map(i -> producersProcedureActive.remove((int)i)) .orElseGet(() -> { logger.error("No producer found with POD [" + pod + "]"); return null; });
Holger
0

Die Aufgabe ist: Holen Sie sich ✶ und ✶ entfernen Sie das Element aus der Liste

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
  } ) );
Kaplan
quelle