2016-02-29 19 views
18

Angesichts einer Liste von Elementen, möchte ich das Element mit einer bestimmten Eigenschaft und entfernen Sie es aus der Liste. Die beste Lösung, die ich fand, ist:Java 8 Lambda Holen und Entfernen Element aus der Liste

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

Ist es möglich zu kombinieren und in einem Lambda-Ausdruck zu entfernen?

+7

Dies scheint wirklich ein klassischer Fall, wenn nur eine Schleife und Iterator statt. – chrylis

+0

@chrylis Ich stimme nicht zu;) Wir sind so an die imperative Programmierung gewöhnt, dass jeder andere Weg zu exotisch klingt. Stellen Sie sich vor, die Realität wäre umgekehrt: Wir sind an die funktionale Programmierung gewöhnt und Java fügt ein neues Imperativ hinzu. Würdest du sagen, dass dies der klassische Fall für Streams, Prädikate und Optionals wäre? –

+5

Rufe 'get()' nicht hier auf! Du hast 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, oderElse, oderElseGet oder orElseThrow. –

Antwort

8

Die direkte Lösung wäre ifPresent(consumer) auf der Optional von findFirst() zurückgegeben werden. Dieser Consumer wird aufgerufen, wenn das optionale nicht leer ist. Der Vorteil ist auch, dass es keine Ausnahme auslöst, wenn die Suche einen leeren optionalen Wert 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 die Optional auf das Ergebnis der mapremove Aufruf:

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

Aber beachten Sie, dass die remove(Object) Operation wird wieder die Liste durchlaufen, das Element zu finden zu entfernen. Wenn Sie eine Liste mit zufälligem Zugriff haben, wie ein ArrayList, wäre es besser, einen Strom, der über die Indizes der Liste zu machen und die erste Immersions das Prädikats finden:

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

Mit dieser Lösung des remove(int) Betrieb wirkt direkt auf den Index.

+0

Ich denke, wir können die Kosten für das Boxen eines einzigen "int" -Wert ignorieren ... – Holger

+2

Dies ist pathologisch für eine verknüpfte Liste. – chrylis

+1

@chrylis Die Indexlösung wäre in der Tat. Abhängig von der Listenimplementation würde man eine gegenüber der anderen bevorzugen. Einen kleinen Schnitt gemacht. – Tunaki

3

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] entweder halten das gefundene Element oder null sein.

Der "Trick" hier ist die Umgehung der "effektiv endgültigen" Problem durch die Verwendung eines Array Referenz, die effektiv endgültig ist, aber die Einstellung ihres ersten Elements.

+1

In diesem Fall ist es nicht so schlimm, aber keine Verbesserung gegenüber der Möglichkeit, '.orElse (null)' aufzurufen, um den 'ProducerDTO' oder' null' zu erhalten ... – Holger

+0

In diesem Fall könnte es einfacher sein, es einfach zu haben '.orElse (null)' und habe ein 'if', nein? – Tunaki

+0

@Holger aber wie kann man 'remove()' auch mit 'orElse (null)'? – Bohemian

11

Betrachten Vanille Java-Iteratoren mit der 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 schlicht und klar.
  2. Es wird nur einmal und nur bis zum passenden Element durchlaufen.
  3. Sie können es auf jedem Iterable auch ohne stream() Unterstützung (mindestens die Implementierung remove() auf ihrem Iterator) tun.

Nachteile:

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

Was die

an Ort und Stelle tun

Ist es möglich zu kombiniere get und entferne in einem Lambda-Ausdruck?

anderen Antworten zeigen deutlich, dass es möglich ist, aber man sollte von

  1. Suchen und Entfernen kann die Liste zweimal
  2. ConcurrentModificationException kann geworfen werden durchqueren sich bewusst sein, wenn sie aus der Liste zu entfernen Element ist iterierter
+4

Ich mag diese Lösung, aber beachten Sie, dass es einen schwerwiegenden Nachteil, den Sie verpasst haben: viele Iterable-Implementierungen haben 'remove()' Methoden, die UOE werfen. (Natürlich nicht die für JDK-Sammlungen, aber ich finde es unfair zu sagen "funktioniert bei jedem Iterable".) –

+0

Ich denke, wir könnten annehmen, dass wenn ein Element * im Allgemeinen * entfernt werden kann, es durch * entfernt werden kann Iterator * –

+3

Das könnte man annehmen, aber wenn man sich Hunderte von Iterator-Implementierungen angesehen hat, wäre das eine schlechte Annahme. (Ich mag den Ansatz immer noch; du übertreibst ihn einfach.) –

2

mit Eclipse Collections können Sie detectIndex zusammen mit remove(int) auf jedem 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 MutableList Typen von Eclipse-Sammlungen verwenden, können Sie die detectIndex Methode direkt auf der Liste nennen.

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 Kollektionen

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 an Ort und Stelle ändern wollen, kann sie sowieso nicht als "echte" funktionale Programmierung betrachtet werden. Aber Sie könnten Collectors.partitioningBy() verwenden, um eine neue Liste mit Elementen zu erhalten, die Ihre Bedingung erfüllen, und eine neue Liste von denen, die das nicht tun. Wenn Sie mehrere Elemente haben, die die Bedingung erfüllen, werden natürlich alle diese Elemente in dieser Liste aufgeführt, und nicht nur die erste.

+0

Viel besser, den Stream zu filtern und Ergebnisse in einer neuen Liste zu sammeln –

0

Kombination meiner ersten Idee und Ihre Antworten erreichte ich, was die Lösung auf meine eigene Frage zu sein scheint:

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

Es lässt das Objekt entfernen remove(int) verwenden, die durchqueren nicht wieder die Liste (wie vorgeschlagen von @Tunaki) und lässt es das entfernte Objekt zu der Funktionsaufrufer zurückgeben.

Ich las Ihre Antworten, die vorschlagen, dass ich sichere Methoden wie ifPresent anstelle von get wählen, aber ich finde keine Möglichkeit, sie in diesem Szenario zu verwenden.

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

bearbeiten folgende @Holger Beratung

Dies sollte die Funktion brauchte ich

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; 
      }); 
} 
+2

Sie sollten 'get' nicht verwenden und die Ausnahme abfangen. Das ist nicht nur ein schlechter Stil, sondern kann auch eine schlechte Leistung verursachen. Die saubere Lösung ist noch einfacher, 'return/* stream operation * /. FindFirst() .map (i -> productsProcedureActive.remove ((int) i)) .oderElseGet (() -> {logger.error (" Kein Produzent gefunden mit POD ["+ pod +"] "; zurückgeben null;});' – Holger

7

Element zu entfernen aus der Liste

objectA.removeIf (x -> Bedingungen) ;

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

OUTPUT: A B C

+0

Ich habe die Beschreibung aktualisiert –

2

Obwohl der Faden ist ziemlich alt, dachte noch Lösung bieten - mit Java8.

Verwenden Sie die Funktion removeIf. Zeitkomplexität ist O(n)

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

API-Referenz: removeIf docs

Annahme: producersProcedureActive ist ein List

HINWEIS: Mit diesem Ansatz werden Sie nicht in der Lage sein, den Halt des gelöschten Elements zu erhalten.

+0

Zusätzlich zum Löschen des Elements aus der Liste möchte das OP immer noch einen Verweis auf das Element. – eee

+0

@eee: Vielen Dank für das Hinzeigen. Ich habe diesen Teil aus der ursprünglichen OP-Frage übersehen. – asifsid88

Verwandte Themen