2015-05-04 14 views
39

Wie fast jeder, lerne ich immer noch die Feinheiten der neuen Java 8 Streams API (und liebe sie). Ich habe eine Frage bezüglich der Nutzung von Streams. Ich werde ein vereinfachtes Beispiel bereitstellen.Können Java 8-Streams auf einem Objekt in einer Sammlung arbeiten und es dann entfernen?

Java Streams ermöglicht es uns, eine Collection zu nehmen, und verwenden Sie die stream() Methode darauf, um einen Strom von all seinen Elementen zu erhalten. Darin gibt es eine Reihe von nützlichen Methoden, wie filter(), map() und forEach(), die es uns ermöglichen, Lambda-Operationen auf den Inhalt zu verwenden.

Ich habe Code, der etwa so aussieht (vereinfacht):

set.stream().filter(item -> item.qualify()) 
    .map(item -> (Qualifier)item).forEach(item -> item.operate()); 
set.removeIf(item -> item.qualify()); 

Die Idee ist, eine Zuordnung aller Elemente in der Menge zu erhalten, die eine gewisse Kennzeichnerabgleichung, und dann durch sie arbeiten. Nach der Operation dienen sie keinem weiteren Zweck mehr und sollten aus dem ursprünglichen Satz entfernt werden. Der Code funktioniert gut, aber ich kann das Gefühl nicht loswerden, dass es eine Operation in Stream gibt, die das für mich in einer einzigen Zeile tun könnte.

Wenn es in den Javadocs ist, kann ich es übersehen.

Kennt jemand mehr mit der API so etwas?

Antwort

72

Sie können es wie folgt tun:

set.removeIf(item -> { 
    if (!item.qualify()) 
     return false; 
    item.operate(); 
    return true; 
}); 

Wenn item.operate() immer true kehrt es auf den Punkt sehr tun können.

set.removeIf(item -> item.qualify() && item.operate()); 

Allerdings mag ich diese Ansätze nicht, da es nicht sofort klar ist, was vor sich geht. Persönlich würde ich weiterhin eine for Schleife und eine Iterator dafür verwenden.

for (Iterator<Item> i = set.iterator(); i.hasNext();) { 
    Item item = i.next(); 
    if (item.qualify()) { 
     item.operate(); 
     i.remove(); 
    } 
} 
+7

+1 für die Verwendung einer for-Schleife; Verwenden Sie keine Streams, nur damit Sie Ihren Code in einer Zeile platzieren können. Loops sind oft lesbarer als Streaming-Lösungen, auch wenn sie ausführlicher sind. – dimo414

+3

Ich zögere etwas, wenn ich den statusverändernden Code in 'removeIf()' setze, obwohl ich glaube, dass das theoretisch funktionieren würde. Außerdem eignet sich meine Situation für die funktionale Programmierung, so dass mein Bedarf an Streams über das Packen von allem in eine einzige Zeile hinausgeht; aber danke. –

+3

Die Verwendung von 'removeIf' mit Lambda-Ausdruck ist absolut legitim und sollte auf diese Weise verwendet werden. Statusänderung ist Teil der Sammlungs-API, nicht Stream. – hussachai

1

Nein, Ihre Implementierung ist wahrscheinlich die einfachste. Sie könnten etwas tief Böses tun, indem Sie den Zustand im removeIf Prädikat ändern, aber bitte nicht. Auf der anderen Seite könnte es sinnvoll sein, zu einer Iterator-basierten imperativen Implementierung zu wechseln, die für diesen Anwendungsfall tatsächlich geeigneter und effizienter ist.

+0

Ich habe darüber nachgedacht, aber für meine tatsächliche (nicht vereinfachte) Implementierung könnten die Streams enorm werden. Ich betreibe das im Allgemeinen auf vier bis acht Kernmaschinen; Trotz der Jugend von Java Stream API bin ich noch nicht überzeugt, dass Iteratoren ein Vorteil sein werden. 'parallel()' könnte hier ein Retter sein. Vielen Dank für die schnelle Antwort, obwohl! –

5

In einer Zeile nicht, aber vielleicht könnte man die Verwendung des partitioningBy Sammler machen:

Map<Boolean, Set<Item>> map = 
    set.stream() 
     .collect(partitioningBy(Item::qualify, toSet())); 

map.get(true).forEach(i -> ((Qualifier)i).operate()); 
set = map.get(false); 

Es könnte effizienter sein, da es eine Iteration des Satzes zweimal vermeidet, einen für den Strom Filterung und dann eine für Entfernen von entsprechenden Elementen.

Ansonsten denke ich, Ihre Vorgehensweise ist relativ gut.

2

Was Sie wirklich tun möchten, ist Ihre Partition zu partitionieren. Leider ist in Java 8 die Partitionierung nur über die Terminal "Collect" -Methode möglich.Sie am Ende mit etwas wie folgt aus:

// test data set 
Set<Integer> set = ImmutableSet.of(1, 2, 3, 4, 5); 
// predicate separating even and odd numbers 
Predicate<Integer> evenNumber = n -> n % 2 == 0; 

// initial set partitioned by the predicate 
Map<Boolean, List<Integer>> partitioned = set.stream().collect(Collectors.partitioningBy(evenNumber)); 

// print even numbers 
partitioned.get(true).forEach(System.out::println); 
// do something else with the rest of the set (odd numbers) 
doSomethingElse(partitioned.get(false)) 

Aktualisiert:

Scala Version des Codes über

val set = Set(1, 2, 3, 4, 5) 
val partitioned = set.partition(_ % 2 == 0) 
partitioned._1.foreach(println) 
doSomethingElse(partitioned._2)` 
+0

Ah, hoffentlich werden sie eines Tages eine "' forEachAndRemove'' Methode implementieren. Ich glaube nicht, dass mein Code klarer oder schneller wäre, wenn ich das täte. Danke für die Hilfe! –

+0

Überraschenderweise brauchen Sie keine spezifischen Methoden wie diese. Der gleiche Code in Scala macht viel mehr Sinn (siehe oben). –

1

wenn ich Ihre Frage richtig verstanden:

set = set.stream().filter(item -> { 
    if (item.qualify()) { 
     ((Qualifier) item).operate(); 
     return false; 
    } 
    return true; 
}).collect(Collectors.toSet()); 
0

Nach der Operation dienen sie keinem weiteren Zweck, und sho Sollen aus dem ursprünglichen Satz entfernt werden. Der Code funktioniert gut, aber ich kann das Gefühl nicht loswerden, dass es in Stream eine Operation gibt, die das für mich in einer einzigen Zeile erledigen könnte.

Sie können Elemente aus der Quelle des Streams mit dem Stream nicht entfernen. Von der Javadoc:

meisten Stream-Operationen akzeptieren Parameter, die vom Benutzer angegebenen Verhalten beschreiben ..... richtige Verhalten zu erhalten, diese Verhaltensparameter:

  • müssen nicht störenden (sie tun die Stream-Quelle nicht modifizieren); und
  • müssen in den meisten Fällen zustandslos sein (ihr Ergebnis sollte nicht von einem Zustand abhängen, der sich während der Ausführung der Stream-Pipeline ändern könnte).
-3
user.getSongs() 
    .stream() 
    .filter(song -> song.getSinger().getId() != singerId) // Only those songs where singer ID doesn't match 
    .forEach(song -> user.getSongs().remove(song)); // Then remove them 
0

Ich sehe Sorge Paulus Klarheit, wenn Ströme verwendet wird, in der Top-Antwort angegeben. Vielleicht erklärt das Hinzufügen einer Erklärungsvariablen die Absichten ein wenig.

Verwandte Themen