Ich kann Streams oder zusätzliche Elemente wie folgt hinzufügen:
Stream stream = Stream.concat(stream1, Stream.concat(stream2, Stream.of(element));
Und ich kann im Laufe der Zeit neue Dinge hinzufügen:
Stream stream = Stream.concat(
Stream.concat(
stream1.filter(x -> x!=0), stream2)
.filter(x -> x!=1),
Stream.of(element))
.filter(x -> x!=2);
Aber das ist hässlich, weil concat
es 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 äquivalente Instanzmethode?
2) Gibt es auf jeden Fall einen besseren Weg, dies zu tun?
java
concat
java-8
java-stream
MarcG
quelle
quelle
Antworten:
Wenn Sie statische Importe für Stream.concat und Stream.of hinzufügen , könnte das erste Beispiel wie folgt geschrieben werden:
Das Importieren statischer Methoden mit generischen Namen kann zu Code führen, der schwer zu lesen und zu warten ist ( Namespace-Verschmutzung ). Daher ist es möglicherweise besser, eigene statische Methoden mit aussagekräftigeren Namen zu erstellen . Zur Demonstration werde ich mich jedoch an diesen Namen halten.
Mit diesen beiden statischen Methoden (optional in Kombination mit statischen Importen) könnten die beiden Beispiele wie folgt geschrieben werden:
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 können Collectors verwendet werden, um die Funktionalität von Streams zu erweitern. Mit den beiden Sammlern unten könnten die beiden Beispiele wie folgt geschrieben werden:
Der einzige Unterschied zwischen Ihrer gewünschten Syntax und der obigen Syntax besteht darin, dass Sie concat (...) durch collect (concat (...)) ersetzen müssen . Die beiden statischen Methoden können wie folgt implementiert werden (optional in Kombination mit statischen Importen verwendet):
Natürlich gibt es bei dieser Lösung einen Nachteil, der erwähnt werden sollte. Das Sammeln ist eine letzte Operation, die alle Elemente des Streams verbraucht. Darüber hinaus erstellt der Collector Concat bei jeder Verwendung in der Kette eine Zwischen- ArrayList . Beide Vorgänge können erhebliche Auswirkungen auf das Verhalten Ihres Programms haben. Wenn jedoch die Lesbarkeit wichtiger ist als die Leistung , ist dies möglicherweise immer noch ein sehr hilfreicher Ansatz.
quelle
concat
Sammler nicht gut lesbar. Es scheint seltsam, eine statische Einzelparameter-Methode wie diese zu haben und sie auchcollect
für die Verkettung zu verwenden.It's a bad idea to import static methods with names
? Ich bin wirklich interessiert - ich finde, dass der Code dadurch prägnanter und lesbarer wird, und viele Leute, die ich gefragt hatte, dachten dasselbe. Möchten Sie einige Beispiele nennen, warum das im Allgemeinen schlecht ist?compare(reverse(getType(42)), of(6 * 9).hashCode())
? Beachten Sie, dass ich nicht gesagt habe, dass statische Importe eine schlechte Idee sind, aber statische Importe für generische Namen wieof
undconcat
.Leider ist diese Antwort wahrscheinlich wenig oder gar nicht hilfreich, aber ich habe eine forensische Analyse der Java Lambda-Mailingliste durchgeführt, um festzustellen, ob ich die Ursache für dieses Design finden konnte. Das habe ich herausgefunden.
Am Anfang gab es 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 Probleme, 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 denke 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 Methode concat instance bei Verwendung mit Nullargumenten in Frage stellten.
Dieser andere Thread zeigt jedoch, dass das Design der Concat-Methode diskutiert wurde.
Refactored to Streams.concat (Stream, Stream)
Aber ohne jede 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 ein wenig Licht auf diese Änderung wirft, 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 durchgeführt haben, in dem sie vorgeschlagen haben, die
concat
Methode ausStream
und in die Helferklasse zu verschiebenStreams
.Refactored to Stream.concat (Stream, Stream)
Später wurde es wieder von
Streams
nach verschobenStream
, aber noch einmal keine Erklärung dafür.Unterm Strich ist der Grund für das Design für mich nicht ganz klar und ich konnte keine gute Erklärung finden. Ich denke, Sie könnten die Frage immer noch in der Mailingliste stellen.
Einige Alternativen zur Stream-Verkettung
In diesem anderen Thread von Michael Hixson werden andere Möglichkeiten zum Kombinieren / Konzentrieren von Streams erörtert
quelle
public static <T> Stream<T> concat(Stream<T>... streams) { return Stream.of(streams).reduce(Stream.empty(), Stream::concat);}
@SafeVarargs private static <T> Stream<T> concat(Stream<? extends T>... streams) { return Stream.of(streams).reduce(Stream.empty(),Stream::concat).map(Function.identity());}
Function.identity()
Karte? Immerhin gibt es das gleiche Argument zurück, das es erhalten hat. Dies sollte im resultierenden Stream keine Auswirkung haben. Vermisse ich etwasreturn Stream.of(streams).reduce(Stream.empty(),Stream::concat)
Gibt Stream <zurück? erweitert T>. (Someting <T> ist der Subtyp von Something <? erweitert T>, nicht in die andere Richtung, sodass es nicht gewirkt werden konnte.) Zusätzliche.map(identity())
Besetzung <? erweitert T> bis <T>. Dies geschieht dank einer Mischung aus Java 8-Zieltypen von Methodenargumenten und Rückgabetypen sowie der Signatur der map () -Methode. Eigentlich ist es Function. <T> identity ().? extends T
, da Sie die Capture-Konvertierung verwenden können . Hier ist auf jeden Fall mein Kerncode-Snippet. Lassen Sie uns die Diskussion im Kern fortsetzen.Meine StreamEx- Bibliothek erweitert die Funktionalität der Stream-API. Insbesondere bietet es Methoden wie Anhängen und Voranstellen, die dieses Problem lösen (intern verwenden sie
concat
). Diese Methoden können entweder einen anderen Stream oder eine andere Sammlung oder ein anderes Varargs-Array akzeptieren. Mit meiner Bibliothek kann Ihr Problem auf diese Weise gelöst werden (beachten Sie, dass esx != 0
für nicht-primitive Streams seltsam aussieht):Übrigens gibt es auch eine Verknüpfung für Ihre
filter
Operation:quelle
Mach einfach:
Wo
identity()
ist ein statischer Import vonFunction.identity()
.Das Verketten mehrerer Streams zu einem Stream entspricht dem Reduzieren eines Streams.
Leider gibt es aus irgendeinem Grund keine
flatten()
MethodeStream
, sodass Sie sieflatMap()
mit der Identitätsfunktion verwenden müssen.quelle
Sie können die Guava- Methode verwenden, die bei statischen Importen sehr kurz ist:
Streams
.
concat(Stream<? extends T>... streams)
quelle
Wenn es Ihnen nichts ausmacht, Bibliotheken von Drittanbietern zu verwenden, verfügt cyclops-react über einen erweiterten Stream-Typ, mit dem Sie genau das über die Operatoren append / prepend ausführen können.
Einzelne Werte, Arrays, Iterables, Streams oder Reactive-Streams Publisher können als Instanzmethoden angehängt und vorangestellt werden.
[Offenlegung Ich bin der Hauptentwickler von Cyclops-React]
quelle
Letztendlich bin ich nicht daran interessiert, Streams zu kombinieren, sondern das kombinierte Ergebnis der Verarbeitung jedes Elements all dieser Streams zu erhalten.
Während sich das Kombinieren von Streams als umständlich erweisen kann (daher dieser Thread), ist das Kombinieren ihrer Verarbeitungsergebnisse ziemlich einfach.
Der zu lösende Schlüssel 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.
quelle
Wie wäre es mit einer eigenen Concat-Methode?
Dies macht zumindest Ihr erstes Beispiel viel lesbarer.
quelle