it-swarm.com.de

Hinzufügen von zwei Java 8-Streams oder eines zusätzlichen Elements zu einem Stream

Ich kann Streams oder zusätzliche Elemente hinzufügen, wie folgt:

Stream stream = Stream.concat(stream1, Stream.concat(stream2, Stream.of(element));

Und ich kann neue Sachen hinzufügen, während ich gehe:

Stream stream = Stream.concat(
                       Stream.concat(
                              stream1.filter(x -> x!=0), stream2)
                              .filter(x -> x!=1),
                                  Stream.of(element))
                                  .filter(x -> x!=2);

Das ist aber hässlich, weil concat statisch ist. Wenn concat eine Instanzmethode wäre, wären die obigen Beispiele viel einfacher zu lesen:

 Stream stream = stream1.concat(stream2).concat(element);

Und

 Stream stream = stream1
                 .filter(x -> x!=0)
                 .concat(stream2)
                 .filter(x -> x!=1)
                 .concat(element)
                 .filter(x -> x!=2);

Meine Frage ist:

1) Gibt es einen guten Grund, warum concat statisch ist? Oder fehlt mir eine gleichwertige Instanzmethode?

2) Gibt es auf jeden Fall einen besseren Weg, dies zu tun?

151
MarcG

Wenn Sie statische Importe für Stream.concat und Stream.of hinzufügen, könnte das erste Beispiel folgendermaßen geschrieben werden:

Stream<Foo> stream = concat(stream1, concat(stream2, of(element)));

Beim Importieren von statischen Methoden mit generischen Namen kann es schwierig werden, Code zu lesen und zu verwalten (Namespace-Verschmutzung). Daher ist es möglicherweise besser, ein eigenes statische Methoden mit aussagekräftigeren Namen zu erstellen. Zur Demonstration bleibe ich jedoch bei diesem Namen.

public static <T> Stream<T> concat(Stream<? extends T> lhs, Stream<? extends T> rhs) {
    return Stream.concat(lhs, rhs);
}
public static <T> Stream<T> concat(Stream<? extends T> lhs, T rhs) {
    return Stream.concat(lhs, Stream.of(rhs));
}

Mit diesen beiden statischen Methoden (optional in Kombination mit statischen Importen) können die beiden Beispiele wie folgt geschrieben werden:

Stream<Foo> stream = concat(stream1, concat(stream2, element));

Stream<Foo> stream = concat(
                         concat(stream1.filter(x -> x!=0), stream2).filter(x -> x!=1),
                         element)
                     .filter(x -> x!=2);

Der Code ist jetzt deutlich kürzer. Ich stimme jedoch zu, dass sich die Lesbarkeit nicht verbessert hat. Ich habe also eine andere Lösung.


In vielen Situationen kann Collectors verwendet werden, um Erweitern die Funktionalität von Streams zu bestimmen. Mit den beiden Collectors unten könnten die zwei Beispiele wie folgt geschrieben werden:

Stream<Foo> stream = stream1.collect(concat(stream2)).collect(concat(element));

Stream<Foo> stream = stream1
                     .filter(x -> x!=0)
                     .collect(concat(stream2))
                     .filter(x -> x!=1)
                     .collect(concat(element))
                     .filter(x -> x!=2);

Der einzige Unterschied zwischen Ihrer gewünschten Syntax und der obigen Syntax ist, dass Sie concat (...) durch collect (concat (...)) ersetzen müssen. Die zwei statischen Methoden können wie folgt implementiert werden (optional in Kombination mit statischen Importen verwendet):

private static <T,A,R,S> Collector<T,?,S> combine(Collector<T,A,R> collector, Function<? super R, ? extends S> function) {
    return Collector.of(
        collector.supplier(),
        collector.accumulator(),
        collector.combiner(),
        collector.finisher().andThen(function));
}
public static <T> Collector<T,?,Stream<T>> concat(Stream<? extends T> other) {
    return combine(Collectors.toList(),
        list -> Stream.concat(list.stream(), other));
}
public static <T> Collector<T,?,Stream<T>> concat(T element) {
    return concat(Stream.of(element));
}

Natürlich gibt es bei dieser Lösung einen Nachteil, der erwähnt werden sollte. collect ist eine letzte Operation, die alle Elemente des Streams verbraucht. Darüber hinaus erstellt der Collector concat bei jeder Verwendung in der Kette ein Intermediate ArrayList. Beide Vorgänge können das Verhalten Ihres Programms erheblich beeinflussen. Wenn jedoch Lesbarkeit wichtiger ist als Leistung, kann dies dennoch ein sehr hilfreicher Ansatz sein.

120
nosid

Leider ist diese Antwort wahrscheinlich wenig oder gar keine Hilfe, aber ich habe eine forensische Analyse der Java Lambda Mailing-Liste durchgeführt, um herauszufinden, ob ich die Ursache für dieses Design finden kann. Das habe ich herausgefunden.

Am Anfang stand eine Instanzmethode für Stream.concat (Stream)

In der Mailingliste kann ich deutlich sehen, dass die Methode ursprünglich als Instanzmethode implementiert wurde, wie Sie in diesem Thread von Paul Sandoz über die concat-Operation lesen können.

Darin diskutieren sie die Fragen, die sich aus den Fällen ergeben könnten, in denen der Stream unendlich sein könnte, und welche Verkettung in diesen Fällen bedeuten würde, aber ich glaube nicht, dass dies der Grund für die Änderung war.

In diesem anderen Thread sehen Sie, dass einige frühe Benutzer des JDK 8 das Verhalten der concat-Instanzmethode befragten, wenn sie mit NULL-Argumenten verwendet werden.

Dieser andere Thread zeigt jedoch, dass das Design der concat-Methode diskutiert wurde.

Refresored zu Streams.concat (Stream, Stream)

Aber ohne Erklärung wurden die Methoden plötzlich in statische Methoden geändert, wie Sie in diesem Thread über das Kombinieren von Streams sehen können. Dies ist vielleicht der einzige Mail-Thread, der etwas Licht in diese Änderung bringt, aber es war mir nicht klar genug, um den Grund für das Refactoring zu bestimmen. Aber wir können sehen, dass sie ein Commit gemacht haben , in dem sie vorschlugen, die concat-Methode aus Stream in die Hilfsklasse Streams zu verschieben. 

Ref.-Stream zu Stream.concat (Stream, Stream)

Später wurde es wurde erneut verschoben von Streams nach Stream, aber auch hier keine Erklärung dafür.

Unter dem Strich ist der Grund für das Design für mich nicht völlig klar und ich konnte keine gute Erklärung finden. Ich denke, Sie könnten die Frage immer noch in der Mailingliste stellen.

Einige Alternativen für die Stream-Verkettung

Dieser andere Thread von Michael Hixson diskutiert/fragt nach anderen Möglichkeiten,/concat-Streams zu kombinieren

  1. Um zwei Streams zu kombinieren, sollte ich Folgendes tun:

    Stream.concat(s1, s2)
    

    nicht das:

    Stream.of(s1, s2).flatMap(x -> x)
    

    ... Recht?

  2. Um mehr als zwei Streams zu kombinieren, sollte ich Folgendes tun:

    Stream.of(s1, s2, s3, ...).flatMap(x -> x)
    

    nicht das:

    Stream.of(s1, s2, s3, ...).reduce(Stream.empty(), Stream::concat)
    

    ... Recht?

155
Edwin Dalorzo

Meine StreamEx library erweitert die Funktionalität der Stream-API. Es bietet insbesondere Methoden wie append und prepend , die dieses Problem lösen (intern verwenden sie concat). Diese Methoden können entweder einen anderen Stream oder eine andere Sammlung oder ein Varargs-Array akzeptieren. Mit meiner Bibliothek kann Ihr Problem auf diese Weise gelöst werden (beachten Sie, dass x != 0 für einen nicht primitiven Stream seltsam aussieht):

Stream<Integer> stream = StreamEx.of(stream1)
             .filter(x -> !x.equals(0))
             .append(stream2)
             .filter(x -> !x.equals(1))
             .append(element)
             .filter(x -> !x.equals(2));

Übrigens gibt es auch eine Verknüpfung für Ihre filter-Operation:

Stream<Integer> stream = StreamEx.of(stream1).without(0)
                                 .append(stream2).without(1)
                                 .append(element).without(2);
12
Tagir Valeev

Mach einfach:

Stream.of(stream1, stream2, Stream.of(element)).flatMap(identity());

dabei ist identity() ein statischer Import von Function.identity().

Das Verknüpfen mehrerer Streams zu einem Stream entspricht der Reduzierung eines Streams.

Leider gibt es aus irgendeinem Grund keine flatten()-Methode für Stream. Daher müssen Sie flatMap() mit der Identitätsfunktion verwenden.

9
herman

Letztendlich bin ich nicht daran interessiert, Streams zu kombinieren, sondern das kombinierte Ergebnis der Verarbeitung jedes Elements aller dieser Streams zu erhalten.

Während sich das Kombinieren von Streams als umständlich erweisen kann (daher dieser Thread), ist das Kombinieren der Verarbeitungsergebnisse fair.

Der Schlüssel zur Lösung besteht darin, einen eigenen Kollektor zu erstellen und sicherzustellen, dass die Lieferantenfunktion für den neuen Kollektor jedes Mal dieselbe Sammlung zurückgibt (keine neue). Der folgende Code veranschaulicht diesen Ansatz.

package scratchpad;

import Java.util.ArrayList;
import Java.util.Arrays;
import Java.util.List;
import Java.util.stream.Collector;
import Java.util.stream.Stream;

public class CombineStreams {
    public CombineStreams() {
        super();
    }

    public static void main(String[] args) {
        List<String> resultList = new ArrayList<>();
        Collector<String, List<String>, List<String>> collector = Collector.of(
                () -> resultList,
                (list, item) -> {
                    list.add(item);
                },
                (llist, rlist) -> {
                    llist.addAll(rlist);
                    return llist;
                }
        );
        String searchString = "Wil";

        System.out.println("After processing first stream\n"
                + createFirstStream().filter(name -> name.contains(searchString)).collect(collector));
        System.out.println();

        System.out.println("After processing second stream\n"
                + createSecondStream().filter(name -> name.contains(searchString)).collect(collector));
        System.out.println();

        System.out.println("After processing third stream\n"
                + createThirdStream().filter(name -> name.contains(searchString)).collect(collector));
        System.out.println();

    }

    private static Stream<String> createFirstStream() {
        return Arrays.asList(
                "William Shakespeare",
                "Emily Dickinson",
                "H. P. Lovecraft",
                "Arthur Conan Doyle",
                "Leo Tolstoy",
                "Edgar Allan Poe",
                "Robert Ervin Howard",
                "Rabindranath Tagore",
                "Rudyard Kipling",
                "Seneca",
                "John Donne",
                "Sarah Williams",
                "Oscar Wilde",
                "Catullus",
                "Alfred Tennyson",
                "William Blake",
                "Charles Dickens",
                "John Keats",
                "Theodor Herzl"
        ).stream();
    }

    private static Stream<String> createSecondStream() {
        return Arrays.asList(
                "Percy Bysshe Shelley",
                "Ernest Hemingway",
                "Barack Obama",
                "Anton Chekhov",
                "Henry Wadsworth Longfellow",
                "Arthur Schopenhauer",
                "Jacob De Haas",
                "George Gordon Byron",
                "Jack London",
                "Robert Frost",
                "Abraham Lincoln",
                "O. Henry",
                "Ovid",
                "Robert Louis Stevenson",
                "John Masefield",
                "James Joyce",
                "Clark Ashton Smith",
                "Aristotle",
                "William Wordsworth",
                "Jane Austen"
        ).stream();
    }

    private static Stream<String> createThirdStream() {
        return Arrays.asList(
                "Niccolò Machiavelli",
                "Lewis Carroll",
                "Robert Burns",
                "Edgar Rice Burroughs",
                "Plato",
                "John Milton",
                "Ralph Waldo Emerson",
                "Margaret Thatcher",
                "Sylvie d'Avigdor",
                "Marcus Tullius Cicero",
                "Banjo Paterson",
                "Woodrow Wilson",
                "Walt Whitman",
                "Theodore Roosevelt",
                "Agatha Christie",
                "Ambrose Bierce",
                "Nikola Tesla",
                "Franz Kafka"
        ).stream();
    }
}
1
Legna

Wenn es Ihnen nichts ausmacht, die Bibliotheken von Drittanbietern zu verwenden, verfügt cyclops-react über einen erweiterten Stream-Typ, der es Ihnen ermöglicht, genau dies über die Append/Prepend-Operatoren auszuführen.

Einzelne Werte, Arrays, iterable, Streams oder reaktive Streams Publisher können als Instanzmethoden angehängt und vorangestellt werden.

Stream stream = ReactiveSeq.of(1,2)
                           .filter(x -> x!=0)
                           .append(ReactiveSeq.of(3,4))
                           .filter(x -> x!=1)
                           .append(5)
                           .filter(x -> x!=2);

[Offenlegung Ich bin der Hauptentwickler von Cyclops-React]

1
John McClean

Sie können die Guava-Methode Streams.concat(Stream<? extends T>... streams) verwenden, die beim statischen Import sehr kurz ist:

Stream stream = concat(stream1, stream2, of(element));
1
Kunda

Wie wäre es mit einer eigenen Concat-Methode?

public static Stream<T> concat(Stream<? extends T> a, 
                               Stream<? extends T> b, 
                               Stream<? extends T> args)
{
    Stream<T> concatenated = Stream.concat(a, b);
    for (Stream<T> stream : args)
    {
        concatenated = Stream.concat(concatenated, stream);
    }
    return concatenated;
}

Dies macht zumindest Ihr erstes Beispiel deutlich lesbarer.

0
Felix S