Abrufen einer Liste aus einem java.util.stream.Stream in Java 8

443

Ich habe mit Java 8 Lambdas herumgespielt, um Sammlungen einfach zu filtern. Ich habe jedoch keinen präzisen Weg gefunden, um das Ergebnis als neue Liste innerhalb derselben Anweisung abzurufen. Hier ist mein bisher prägnantester Ansatz:

List<Long> sourceLongList = Arrays.asList(1L, 10L, 50L, 80L, 100L, 120L, 133L, 333L);
List<Long> targetLongList = new ArrayList<>();
sourceLongList.stream().filter(l -> l > 100).forEach(targetLongList::add);

Beispiele im Internet haben meine Frage nicht beantwortet, da sie aufhören, ohne eine neue Ergebnisliste zu erstellen. Es muss einen prägnanteren Weg geben. Ich hätte erwartet , dass, dass die StreamKlasse hat Methoden toList(), toSet()...

Gibt es eine Möglichkeit, die Variablen targetLongListdirekt durch die dritte Zeile zuzuweisen?

Daniel K.
quelle
7
Für den Fall, dass Sie das sourceLongListspäter nicht brauchen, gibt es Collection.removeIf(…)für die Bequemlichkeit.
Holger
2
Wie wäre es damit? List<Long> targetLongList = sourceLongList.stream().collect(Collectors.toList());
Leeyuiwah

Antworten:

609

Was Sie tun, ist möglicherweise der einfachste Weg, vorausgesetzt, Ihr Stream bleibt sequentiell - andernfalls müssen Sie vorher sequential () aufrufen forEach.

[spätere Bearbeitung: Der Grund für den Aufruf von sequential () ist, dass der Code in seiner forEach(targetLongList::add)jetzigen Form ( ) rassig wäre, wenn der Stream parallel wäre. Selbst dann wird der beabsichtigte Effekt nicht erreicht, da dies forEachexplizit nicht deterministisch ist - selbst in einem sequentiellen Stream ist die Reihenfolge der Elementverarbeitung nicht garantiert. Sie müssten verwenden forEachOrdered, um die korrekte Bestellung sicherzustellen. Die Absicht der Stream-API-Designer ist, dass Sie in dieser Situation den Collector verwenden (siehe unten).]

Eine Alternative ist

targetLongList = sourceLongList.stream()
    .filter(l -> l > 100)
    .collect(Collectors.toList());
Maurice Naftalin
quelle
10
Ergänzung: Ich denke, dieser Code wird etwas kürzer, klarer und hübscher, wenn Sie einen statischen Import von verwenden toList. Dazu setzen Sie Folgendes unter die Importe der Datei : static import java.util.stream.Collectors.toList;. Dann liest der Sammelruf gerade .collect(toList()).
Lii
4
In Eclipse kann die IDE einen statischen Import für Methoden hinzufügen. Dazu fügen Sie die CollectorsKlasse unter Einstellungen -> Java -> Editor -> Inhaltsassistent -> Favoriten hinzu . Danach müssen Sie nur noch toLibei gedrückter Strg + Leertaste eingeben , damit die IDE ausgefüllt toListund der statische Import hinzugefügt wird.
Lii
3
Eine Sache, die Sie beachten sollten, ist, dass IntStreameinige andere, aber nicht ganz andere Methoden Streamnicht vorhanden sind collect(Collector)und Sie sie aufrufen müssen IntStream.boxed(), um sie zuerst in eine reguläre zu konvertieren Stream. Andererseits möchten Sie vielleicht nur toArray().
Mutant Bob
Deshalb müssen wir verwenden sequential()vor forEachoder Verwendung ‚forEachOrdered`
Amarnath harish
1
@amarnathharish Da forEach die Reihenfolge der Ausführung des Vorgangs für einen parallelen Stream nicht garantiert. Im JavaDoc heißt es: "Das Verhalten dieser Operation ist explizit nicht deterministisch. Bei parallelen Stream-Pipelines garantiert diese Operation nicht, dass die Begegnungsreihenfolge des Streams eingehalten wird, da dies den Vorteil der Parallelität opfern würde." (Der erste Satz dieses Zitats bedeutet tatsächlich, dass die Reihenfolge auch für sequentielle Streams nicht garantiert ist, obwohl sie in der Praxis erhalten bleibt.)
Maurice Naftalin
183

Aktualisiert:

Ein anderer Ansatz ist zu verwenden Collectors.toList:

targetLongList = 
    sourceLongList.stream().
    filter(l -> l > 100).
    collect(Collectors.toList());

Vorherige Lösung:

Ein anderer Ansatz ist zu verwenden Collectors.toCollection:

targetLongList = 
    sourceLongList.stream().
    filter(l -> l > 100).
    collect(Collectors.toCollection(ArrayList::new));
MohamedSanaulla
quelle
60
Dies ist jedoch nützlich, wenn Sie eine bestimmte List-Implementierung wünschen.
Orbfish
1
Obwohl empfohlen wird, gegen Schnittstellen zu codieren, gibt es eindeutige Fälle (einer davon ist GWT), in denen Sie gegen konkrete Implementierungen codieren müssen (es sei denn, Sie möchten, dass alle List-Implementierungen kompiliert und als Javascript geliefert werden).
Eduard Korenschi
3
Ein weiterer Vorteil dieser Methode aus dem Collectors::toListJavadoc: "Es gibt keine Garantien für den Typ, die Veränderlichkeit, die Serialisierbarkeit oder die Thread-Sicherheit der zurückgegebenen Liste. Wenn mehr Kontrolle über die zurückgegebene Liste erforderlich ist, verwenden Sie toCollection(Supplier)."
Charles Wood
12

Ich verwende gerne eine util-Methode, die einen Kollektor zurückgibt, ArrayListwenn ich dies möchte.

Ich denke, die verwendete Lösung Collectors.toCollection(ArrayList::new)ist für eine so häufige Operation etwas zu laut.

Beispiel:

ArrayList<Long> result = sourceLongList.stream()
    .filter(l -> l > 100)
    .collect(toArrayList());

public static <T> Collector<T, ?, ArrayList<T>> toArrayList() {
    return Collectors.toCollection(ArrayList::new);
}

Mit dieser Antwort möchte ich auch zeigen, wie einfach es ist, benutzerdefinierte Kollektoren zu erstellen und zu verwenden, was im Allgemeinen sehr nützlich ist.

Lii
quelle
Wenn Sie das Ergebnis als List <Long> deklarieren, müssen Sie diese util-Methode nicht verwenden. Collectors.toList reicht aus. Die Verwendung bestimmter Klassen anstelle von Schnittstellen ist auch ein Codegeruch.
Lluis Martinez
1
@LluisMartinez: "Collectors.toList reicht aus." : Nein, nicht in vielen Situationen. Weil es keine gute Idee ist, sie zu verwenden, toListwenn Sie beispielsweise die Liste später im Programm ändern möchten. In der toListDokumentation heißt es: "Es gibt keine Garantien für den Typ, die Veränderlichkeit, die Serialisierbarkeit oder die Thread-Sicherheit der zurückgegebenen Liste. Wenn mehr Kontrolle über die zurückgegebene Liste erforderlich ist, verwenden Sie toCollection." . Meine Antwort zeigt einen Weg, dies in einem häufigen Fall bequemer zu machen.
Lii
Wenn Sie speziell eine ArrayList erstellen möchten, ist dies in Ordnung.
Lluis Martinez
5

Wenn Sie über ein Array von Grundelementen verfügen, können Sie die in Eclipse- Sammlungen verfügbaren Grundelement-Sammlungen verwenden .

LongList sourceLongList = LongLists.mutable.of(1L, 10L, 50L, 80L, 100L, 120L, 133L, 333L);
LongList targetLongList = sourceLongList.select(l -> l > 100);

Wenn Sie die sourceLongList nicht ändern können von List:

List<Long> sourceLongList = Arrays.asList(1L, 10L, 50L, 80L, 100L, 120L, 133L, 333L);
List<Long> targetLongList = 
    ListAdapter.adapt(sourceLongList).select(l -> l > 100, new ArrayList<>());

Wenn Sie verwenden möchten LongStream:

long[] sourceLongs = new long[]{1L, 10L, 50L, 80L, 100L, 120L, 133L, 333L};
LongList targetList = 
    LongStream.of(sourceLongs)
    .filter(l -> l > 100)
    .collect(LongArrayList::new, LongArrayList::add, LongArrayList::addAll);

Hinweis: Ich bin ein Mitarbeiter von Eclipse Collections.

Nikhil Nanivadekar
quelle
4

Ein wenig effizienter Weg (vermeiden Sie das Erstellen der Quellliste und das automatische Entpacken durch den Filter):

List<Long> targetLongList = LongStream.of(1L, 10L, 50L, 80L, 100L, 120L, 133L, 333L)
    .filter(l -> l > 100)
    .boxed()
    .collect(Collectors.toList());
msayag
quelle
1

Wenn es Ihnen nichts ausmacht, Bibliotheken von Drittanbietern zu verwenden, enthält die Cyclops-React- Bibliothek von AOL (Offenlegung, an der ich beteiligt bin) Erweiterungen für alle JDK-Sammlungstypen , einschließlich List . Die ListX-Schnittstelle erweitert java.util.List und fügt eine große Anzahl nützlicher Operatoren hinzu, einschließlich Filter.

Sie können einfach schreiben-

ListX<Long> sourceLongList = ListX.of(1L, 10L, 50L, 80L, 100L, 120L, 133L, 333L);
ListX<Long> targetLongList = sourceLongList.filter(l -> l > 100);

ListX kann auch aus einer vorhandenen Liste erstellt werden (über ListX.fromIterable).

John McClean
quelle
1

Es gibt eine andere Variante der Sammelmethode, die von der LongStream-Klasse und in ähnlicher Weise auch von den IntStream- und DoubleStream-Klassen bereitgestellt wird.

<R> R collect(Supplier<R> supplier,
              ObjLongConsumer<R> accumulator,
              BiConsumer<R,R> combiner)

Führt eine veränderbare Reduktionsoperation für die Elemente dieses Streams durch. Eine veränderbare Verkleinerung ist eine Verkleinerung, bei der der reduzierte Wert ein veränderbarer Ergebniscontainer ist, z. B. eine ArrayList, und Elemente durch Aktualisieren des Ergebnisstatus und nicht durch Ersetzen des Ergebnisses eingefügt werden. Dies ergibt ein Ergebnis, das entspricht:

R result = supplier.get();
  for (long element : this stream)
       accumulator.accept(result, element);
  return result;

Wie beim Reduzieren (long, LongBinaryOperator) können Erfassungsvorgänge parallelisiert werden, ohne dass eine zusätzliche Synchronisierung erforderlich ist. Dies ist eine Terminaloperation.

Die Antwort auf Ihre Frage mit dieser Erhebungsmethode lautet wie folgt:

    LongStream.of(1L, 2L, 3L, 3L).filter(i -> i > 2)
    .collect(ArrayList::new, (list, value) -> list.add(value)
    , (list1, list2) -> list1.addAll(list2));

Unten ist die Methodenreferenzvariante aufgeführt, die ziemlich intelligent ist, aber einige, die schwer zu verstehen sind:

     LongStream.of(1L, 2L, 3L, 3L).filter(i -> i > 2)
    .collect(ArrayList::new, List::add , List::addAll);

Unten wird die HashSet-Variante sein:

     LongStream.of(1L, 2L, 3L, 3).filter(i -> i > 2)
     .collect(HashSet::new, HashSet::add, HashSet::addAll);

Ähnlich sieht die LinkedList-Variante aus:

     LongStream.of(1L, 2L, 3L, 3L)
     .filter(i -> i > 2)
     .collect(LinkedList::new, LinkedList::add, LinkedList::addAll);
Vaneet Kataria
quelle
0

Falls jemand (wie ich) da draußen nach Wegen sucht, mit Objekten umzugehen, anstatt mit primitiven Typen, dann benutze sie mapToObj()

String ss = "An alternative way is to insert the following VM option before "
        + "the -vmargs option in the Eclipse shortcut properties(edit the "
        + "field Target inside the Shortcut tab):";

List<Character> ll = ss
                        .chars()
                        .mapToObj(c -> new Character((char) c))
                        .collect(Collectors.toList());

System.out.println("List type: " + ll.getClass());
System.out.println("Elem type: " + ll.get(0).getClass());
ll.stream().limit(50).forEach(System.out::print);

Drucke:

List type: class java.util.ArrayList
Elem type: class java.lang.Character
An alternative way is to insert the following VM o
Kashyap
quelle
0
String joined = 
                Stream.of(isRead?"read":"", isFlagged?"flagged":"", isActionRequired?"action":"", isHide?"hide":"")
                      .filter(s -> s != null && !s.isEmpty())
                      .collect(Collectors.joining(","));
Sharath
quelle
0

Hier ist Code von AbacusUtil

LongStream.of(1, 10, 50, 80, 100, 120, 133, 333).filter(e -> e > 100).toList();

Offenlegung: Ich bin der Entwickler von AbacusUtil.

user_3380739
quelle
Ich finde keine toList-Methode in der LongStream-Klasse. Könnten Sie diesen Code ausführen?
Vaneet Kataria
@ VanetKataria versuchen com.landawn.abacus.util.stream.LongStreamoder LongStreamExin AbacusUtil
user_3380739
0

Sie können den Code wie folgt umschreiben:

List<Long> sourceLongList = Arrays.asList(1L, 10L, 50L, 80L, 100L, 120L, 133L, 333L);
List<Long> targetLongList = sourceLongList.stream().filter(l -> l > 100).collect(Collectors.toList());
Pratik Pawar
quelle
Danke für deinen Beitrag. Bitte erläutern Sie jedoch, was Sie geändert haben und inwieweit es sich auf die Frage bezieht.
B - rian
Hier habe ich zuerst meine ArrayList konvertiert, um sie mit einem Filter zu dämpfen. Ich filtere die erforderlichen Daten heraus. Schließlich habe ich die Sammelmethode von Java 8 Stream verwendet, um Daten in einer neuen Liste namens targetLongList zu sammeln.
Pratik Pawar
0

So sammeln Sie in einer veränderlichen Liste:

targetList = sourceList.stream()
                       .filter(i -> i > 100) //apply filter
                       .collect(Collectors.toList());

So sammeln Sie in einer unveränderlichen Liste:

targetList = sourceList.stream()
                       .filter(i -> i > 100) //apply filter
                       .collect(Collectors.toUnmodifiableList());

Erklärung von collectaus dem JavaDoc :

Führt mit einem Collector eine veränderbare Reduktionsoperation für die Elemente dieses Streams durch. Ein Collector kapselt die Funktionen, die als zu sammelnde Argumente verwendet werden (Supplier, BiConsumer, BiConsumer), und ermöglicht die Wiederverwendung von Erfassungsstrategien und die Zusammensetzung von Erfassungsvorgängen wie Gruppierung oder Partitionierung auf mehreren Ebenen. Wenn der Stream parallel ist und der Collector gleichzeitig ist und entweder der Stream ungeordnet oder der Collector ungeordnet ist, wird eine gleichzeitige Reduzierung durchgeführt (Einzelheiten zur gleichzeitigen Reduzierung finden Sie unter Collector).

Dies ist eine Terminaloperation.

Bei paralleler Ausführung können mehrere Zwischenergebnisse instanziiert, aufgefüllt und zusammengeführt werden, um die Isolation veränderlicher Datenstrukturen aufrechtzuerhalten. Daher ist auch bei paralleler Ausführung mit nicht threadsicheren Datenstrukturen (wie ArrayList) keine zusätzliche Synchronisation für eine parallele Reduzierung erforderlich.

Saikat
quelle
-3

Wenn Sie dies nicht verwenden parallel(), funktioniert dies

List<Long> sourceLongList = Arrays.asList(1L, 10L, 50L, 80L, 100L, 120L, 133L, 333L);

List<Long> targetLongList =  new ArrayList<Long>();

sourceLongList.stream().peek(i->targetLongList.add(i)).collect(Collectors.toList());
Debapriya Biswas
quelle
Ich mag es nicht, dass collect () nur verwendet wird, um den Stream so zu steuern, dass der peek () - Hook für jedes Element aufgerufen wird. Das Ergebnis der Terminaloperation wird verworfen.
Daniel K.
Es ist sehr seltsam, collectden Rückgabewert aufzurufen und dann nicht zu speichern. In diesem Fall könnten Sie forEachstattdessen verwenden. Aber das ist immer noch eine schlechte Lösung.
Lii
1
Die Verwendung von peek () auf diese Weise ist ein Antimuster.
Grzegorz Piwowarek
Gemäß Stream Java darf die Peek-Methode nur für Debugging-Zwecke verwendet werden. Sie sollte nur für das Debuggen verwendet werden.
Vaneet Kataria