Das neue Java 8-Stream-Framework und seine Freunde sorgen für einen sehr präzisen Java-Code, aber ich bin auf eine scheinbar einfache Situation gestoßen, die schwierig zu formulieren ist.
Betrachten Sie eine List<Thing> things
und Methode Optional<Other> resolve(Thing thing)
. Ich möchte das Thing
s auf Optional<Other>
s abbilden und das erste bekommen Other
. Die naheliegende Lösung wäre die Verwendung things.stream().flatMap(this::resolve).findFirst()
, flatMap
erfordert jedoch , dass Sie einen Stream zurückgeben und Optional
keine stream()
Methode haben (oder ist es eine Collection
oder eine Methode, mit der Sie ihn konvertieren oder als anzeigen können Collection
).
Das Beste, was ich mir einfallen lassen kann, ist Folgendes:
things.stream()
.map(this::resolve)
.filter(Optional::isPresent)
.map(Optional::get)
.findFirst();
Aber das scheint für einen sehr häufigen Fall furchtbar langwierig zu sein. Hat jemand eine bessere Idee?
quelle
.flatMap(Optional::toStream)
, die sich auf Ihre Version bezieht, wenn sie existiert hat. Sie sehen tatsächlich, was los ist.Optional.stream
existiert jetzt in JDK 9 ....Antworten:
Java 9
Optional.stream
wurde zu JDK 9 hinzugefügt. Auf diese Weise können Sie Folgendes ausführen, ohne dass eine Hilfsmethode erforderlich ist:Java 8
Ja, dies war eine kleine Lücke in der API, da es etwas unpraktisch ist, aus einem optionalen Stream einen Stream mit einer Länge von null oder eins zu machen. Sie könnten dies tun:
Es ist jedoch etwas umständlich, den ternären Operator in der flatMap zu haben. Daher ist es möglicherweise besser, eine kleine Hilfsfunktion zu schreiben, um dies zu tun:
Hier habe ich den Aufruf zum Auflösen () eingefügt, anstatt eine separate map () -Operation zu haben, aber dies ist Geschmackssache.
quelle
static <T> Stream<T> streamopt(Optional<T> opt) { return opt.map(Stream::of).orElse(Stream.empty()); }
Optional
Überladung hinzufügenStream#flatMap
... auf diese Weise könnten Sie einfach schreibenstream().flatMap(this::resolve)
Optional.stream()
.Ich füge diese zweite Antwort basierend auf einer vorgeschlagenen Bearbeitung durch den Benutzer srborlongan zu meiner anderen Antwort hinzu . Ich denke, die vorgeschlagene Technik war interessant, aber sie war nicht wirklich als Bearbeitung für meine Antwort geeignet. Andere stimmten zu und die vorgeschlagene Änderung wurde abgelehnt. (Ich war nicht einer der Wähler.) Die Technik hat jedoch ihre Berechtigung. Es wäre am besten gewesen, wenn Srborlongan seine eigene Antwort gepostet hätte. Dies ist noch nicht geschehen, und ich wollte nicht, dass die Technik im Nebel des von StackOverflow abgelehnten Bearbeitungsverlaufs verloren geht. Deshalb habe ich beschlossen, sie selbst als separate Antwort anzuzeigen.
Grundsätzlich besteht die Technik darin, einige der
Optional
Methoden auf clevere Weise zu verwenden, um zu vermeiden, dass ein ternärer Operator (? :
) oder eine if / else-Anweisung verwendet werden muss.Mein Inline-Beispiel würde folgendermaßen umgeschrieben:
Ein Beispiel, das eine Hilfsmethode verwendet, wird folgendermaßen umgeschrieben:
KOMMENTAR
Vergleichen wir die Originalversion mit der modifizierten Version direkt:
Das Original ist ein unkomplizierter, wenn auch fachmännischer Ansatz: Wir bekommen einen
Optional<Other>
; Wenn es einen Wert hat, geben wir einen Stream zurück, der diesen Wert enthält, und wenn es keinen Wert hat, geben wir einen leeren Stream zurück. Ziemlich einfach und leicht zu erklären.Die Modifikation ist clever und hat den Vorteil, dass sie Bedingungen vermeidet. (Ich weiß, dass einige Leute den ternären Operator nicht mögen. Wenn er missbraucht wird, kann dies tatsächlich dazu führen, dass Code schwer zu verstehen ist.) Manchmal können die Dinge jedoch zu klug sein. Der geänderte Code beginnt ebenfalls mit einem
Optional<Other>
. Dann ruft es auf,Optional.map
was wie folgt definiert ist:Der
map(Stream::of)
Anruf gibt eine zurückOptional<Stream<Other>>
. Wenn in der Eingabe Optional ein Wert vorhanden war, enthält die zurückgegebene Option einen Stream, der das einzelne Ergebnis Other enthält. Wenn der Wert jedoch nicht vorhanden war, ist das Ergebnis leer. Optional.Als nächstes gibt der Aufruf von
orElseGet(Stream::empty)
einen Wert vom Typ zurückStream<Other>
. Wenn sein Eingabewert vorhanden ist, erhält er den Wert, der das Einzelelement istStream<Other>
. Andernfalls (wenn der Eingabewert fehlt) wird ein Leerzeichen zurückgegebenStream<Other>
. Das Ergebnis ist also korrekt, genauso wie der ursprüngliche Bedingungscode.In den Kommentaren zu meiner Antwort bezüglich der abgelehnten Bearbeitung hatte ich diese Technik als "prägnanter, aber auch dunkler" beschrieben. Ich stehe dazu. Ich brauchte eine Weile, um herauszufinden, was es tat, und ich brauchte auch eine Weile, um die obige Beschreibung dessen, was es tat, aufzuschreiben. Die Schlüssel-Subtilität ist die Transformation von
Optional<Other>
nachOptional<Stream<Other>>
. Sobald Sie dies verstanden haben, macht es Sinn, aber es war mir nicht klar.Ich werde jedoch anerkennen, dass Dinge, die anfangs dunkel sind, mit der Zeit idiomatisch werden können. Es könnte sein, dass diese Technik der beste Weg in der Praxis ist, zumindest bis sie
Optional.stream
hinzugefügt wird (falls dies jemals der Fall sein sollte).UPDATE:
Optional.stream
wurde zu JDK 9 hinzugefügt.quelle
Sie können es nicht prägnanter machen, als Sie es bereits tun.
Sie behaupten, dass Sie nicht wollen
.filter(Optional::isPresent)
und.map(Optional::get)
.Dies wurde durch die von @StuartMarks beschriebene Methode behoben. Als Ergebnis ordnen Sie es jetzt einem zu
Optional<T>
, sodass Sie jetzt.flatMap(this::streamopt)
undget()
am Ende ein verwenden müssen.Es besteht also immer noch aus zwei Anweisungen und Sie können jetzt Ausnahmen mit der neuen Methode erhalten! Denn was ist, wenn jedes optionale leer ist? Dann
findFirst()
wird der eine leere Option zurückgeben und Ihrget()
wird fehlschlagen!Also was hast du:
ist eigentlich der beste Weg, um das zu erreichen, was Sie wollen, und das heißt, Sie möchten das Ergebnis als
T
, nicht als speichernOptional<T>
.Ich habe mir erlaubt, eine
CustomOptional<T>
Klasse zu erstellen , die das einschließtOptional<T>
und eine zusätzliche Methode bietetflatStream()
. Beachten Sie, dass Sie nicht erweitern könnenOptional<T>
:Sie werden sehen, dass ich
flatStream()
wie hier hinzugefügt habe:Benutzt als:
Sie müssen hier immer noch ein zurückgeben
Stream<T>
, da Sie nicht zurückgeben könnenT
, denn wenn!optional.isPresent()
, dann,T == null
wenn Sie es als solches deklarieren, dann.flatMap(CustomOptional::flatStream)
würden Sie versuchen,null
es einem Stream hinzuzufügen , und das ist nicht möglich.Zum Beispiel:
Benutzt als:
Werft jetzt einen
NullPointerException
in den Stream Operationen.Fazit
Die Methode, die Sie verwendet haben, ist tatsächlich die beste Methode.
quelle
Eine etwas kürzere Version mit
reduce
:Sie können die Reduzierungsfunktion auch in eine statische Dienstprogrammmethode verschieben und dann wird es:
quelle
Da meine vorherige Antwort nicht sehr beliebt zu sein schien, werde ich es noch einmal versuchen.
Eine kurze Antwort:
Sie sind meistens auf dem richtigen Weg. Der kürzeste Code, um zu Ihrer gewünschten Ausgabe zu gelangen, ist folgender:
Dies wird all Ihren Anforderungen entsprechen:
Optional<Result>
this::resolve
nach Bedarf faul anthis::resolve
wird nach dem ersten nicht leeren Ergebnis nicht aufgerufenOptional<Result>
Längere Antwort
Die einzige Änderung im Vergleich zur ursprünglichen OP-Version war, dass ich sie
.map(Optional::get)
vor dem Aufruf entfernt.findFirst()
und.flatMap(o -> o)
als letzten Aufruf in der Kette hinzugefügt habe .Dies hat einen schönen Effekt, wenn das Double-Optional entfernt wird, wenn der Stream ein tatsächliches Ergebnis findet.
In Java kann man nicht kürzer sein.
Das alternative Codeausschnitt, das die konventionellere
for
Schleifentechnik verwendet, besteht aus ungefähr der gleichen Anzahl von Codezeilen und hat mehr oder weniger die gleiche Reihenfolge und Anzahl von Operationen, die Sie ausführen müssen:this.resolve
,Optional.isPresent
Um zu beweisen, dass meine Lösung wie angekündigt funktioniert, habe ich ein kleines Testprogramm geschrieben:
(Es gibt nur wenige zusätzliche Zeilen zum Debuggen und Überprüfen, dass nur so viele Aufrufe wie nötig aufgelöst werden müssen ...)
Wenn ich dies über eine Befehlszeile ausführe, erhalte ich die folgenden Ergebnisse:
quelle
Wenn es Ihnen nichts ausmacht, eine Bibliothek eines Drittanbieters zu verwenden, können Sie Javaslang verwenden . Es ist wie Scala, aber in Java implementiert.
Es kommt mit einer vollständigen unveränderlichen Sammlungsbibliothek, die der von Scala bekannten sehr ähnlich ist. Diese Sammlungen ersetzen die Sammlungen von Java und den Stream von Java 8. Es hat auch eine eigene Implementierung von Option.
Hier ist eine Lösung für das Beispiel der Ausgangsfrage:
Haftungsausschluss: Ich bin der Schöpfer von Javaslang.
quelle
Spät zur Party, aber was ist mit
Sie können das letzte get () entfernen, wenn Sie eine util-Methode zum manuellen Konvertieren von optional in stream erstellen:
Wenn Sie den Stream sofort von Ihrer Auflösungsfunktion zurückgeben, speichern Sie eine weitere Zeile.
quelle
Ich möchte Factory-Methoden zum Erstellen von Hilfsprogrammen für funktionale APIs fördern :
Die Fabrikmethode:
Argumentation:
Wie bei Methodenreferenzen im Allgemeinen können Sie im Vergleich zu Lambda-Ausdrücken nicht versehentlich eine Variable aus dem zugänglichen Bereich erfassen, z.
t -> streamopt(resolve(o))
Es ist zusammensetzbar, Sie können zB
Function::andThen
das Ergebnis der Factory-Methode aufrufen :streamopt(this::resolve).andThen(...)
Während im Fall eines Lambda Sie es zuerst gießen müssen:
((Function<T, Stream<R>>) t -> streamopt(resolve(t))).andThen(...)
quelle
Null wird von dem Stream unterstützt, der von My library AbacusUtil bereitgestellt wird . Hier ist Code:
quelle
Wenn Sie mit Java 8 nicht weiterkommen, aber Zugriff auf Guava 21.0 oder höher haben, können Sie verwenden
Streams.stream
eine Option in einen Stream konvertieren.Also gegeben
Du kannst schreiben
quelle
Was ist damit?
https://stackoverflow.com/a/58281000/3477539
quelle
return list.stream().filter(Optional::isPresent).map(Optional::get).collect(Collectors.toList()))
, genau wie die Frage (und Ihre verknüpfte Antwort) ....get()
ohne verwendenisPresent()
, erhalten Sie eine Warnung in IntelliJHöchstwahrscheinlich machst du es falsch.
Java 8 Optional ist nicht für diese Verwendung vorgesehen. Es ist normalerweise nur für Terminal-Stream-Operationen reserviert, die einen Wert zurückgeben können oder nicht, wie zum Beispiel find.
In Ihrem Fall ist es möglicherweise besser, zuerst zu versuchen, einen kostengünstigen Weg zu finden, um die auflösbaren Elemente herauszufiltern, und dann das erste Element als optional zu erhalten und es als letzten Vorgang aufzulösen. Besser noch - anstatt zu filtern, suchen Sie das erste auflösbare Element und lösen Sie es auf.
Als Faustregel gilt, dass Sie sich bemühen sollten, die Anzahl der Elemente im Stream zu reduzieren, bevor Sie sie in etwas anderes umwandeln. YMMV natürlich.
quelle
Optional<Result> searchFor(Term t)
. Das scheint der Absicht von Optional zu entsprechen. Außerdem sollten stream () s träge ausgewertet werden, sodass keine zusätzliche Arbeit zum Auflösen von Begriffen nach dem ersten übereinstimmenden Begriff erfolgen sollte.