Le nouveau cadre de flux Java 8 et ses amis créent un code java très concis, mais j'ai rencontré une situation apparemment simple qui est difficile à faire de manière concise.
Considérez une List<Thing> things
méthode et Optional<Other> resolve(Thing thing)
. Je veux mapper les Thing
s à Optional<Other>
s et obtenir le premier Other
. La solution évidente serait d'utiliser things.stream().flatMap(this::resolve).findFirst()
, mais flatMap
nécessite que vous renvoyiez un flux et qu'il Optional
n'ait pas de stream()
méthode (ou s'agit-il d'un Collection
ou fournissez une méthode pour le convertir ou l'afficher en tant que Collection
).
Le mieux que je puisse trouver est le suivant:
things.stream()
.map(this::resolve)
.filter(Optional::isPresent)
.map(Optional::get)
.findFirst();
Mais cela semble terriblement long pour ce qui semble être un cas très courant. Quelqu'un a une meilleure idée?
la source
.flatMap(Optional::toStream)
, avec votre version vous voyez réellement ce qui se passe.Optional.stream
existe dans JDK 9 maintenant ....Réponses:
Java 9
Optional.stream
a été ajouté à JDK 9. Cela vous permet d'effectuer les opérations suivantes, sans avoir besoin d'aucune méthode d'assistance:Java 8
Oui, c'était un petit trou dans l'API, en ce sens qu'il est quelque peu gênant de transformer un
Optional<T>
en une longueur nulle ou unStream<T>
. Vous pouvez faire ceci:flatMap
Cependant, avoir l'opérateur ternaire à l'intérieur du est un peu lourd, il serait donc préférable d'écrire une petite fonction d'aide pour le faire:Ici, j'ai souligné l'appel au
resolve()
lieu d'avoir unemap()
opération séparée , mais c'est une question de goût.la source
static <T> Stream<T> streamopt(Optional<T> opt) { return opt.map(Stream::of).orElse(Stream.empty()); }
Optional
surcharge àStream#flatMap
... de cette façon, vous pourriez simplement écrirestream().flatMap(this::resolve)
Optional.stream()
.J'ajoute cette deuxième réponse basée sur une modification proposée par l'utilisateur srborlongan à mon autre réponse . Je pense que la technique proposée était intéressante, mais elle ne convenait pas vraiment comme modification de ma réponse. D'autres ont accepté et la modification proposée a été rejetée. (Je ne faisais pas partie des électeurs.) Cependant, la technique a du mérite. Il aurait été préférable que srborlongan ait affiché sa propre réponse. Cela ne s'est pas encore produit, et je ne voulais pas que la technique soit perdue dans les brumes de l'historique des modifications rejetées par StackOverflow, j'ai donc décidé de le faire apparaître comme une réponse distincte moi-même.
Fondamentalement, la technique consiste à utiliser certaines des
Optional
méthodes de manière intelligente pour éviter d'avoir à utiliser un opérateur ternaire (? :
) ou une instruction if / else.Mon exemple en ligne serait réécrit de cette façon:
Un mon exemple qui utilise une méthode d'assistance serait réécrit de cette façon:
COMMENTAIRE
Comparons directement les versions originales et modifiées:
L'original est une approche simple mais professionnelle: nous obtenons un
Optional<Other>
; s'il a une valeur, nous renvoyons un flux contenant cette valeur, et s'il n'a pas de valeur, nous renvoyons un flux vide. Assez simple et facile à expliquer.La modification est astucieuse et présente l'avantage d'éviter les conditionnels. (Je sais que certaines personnes n'aiment pas l'opérateur ternaire. S'il est mal utilisé, il peut en effet rendre le code difficile à comprendre.) Cependant, parfois les choses peuvent être trop intelligentes. Le code modifié commence également par un
Optional<Other>
. Ensuite, il appelleOptional.map
ce qui est défini comme suit:L'
map(Stream::of)
appel renvoie unOptional<Stream<Other>>
. Si une valeur était présente dans l'entrée Facultatif, le Facultatif renvoyé contient un Flux qui contient le seul résultat Autre. Mais si la valeur n'était pas présente, le résultat est un Facultatif vide.Ensuite, l'appel à
orElseGet(Stream::empty)
renvoie une valeur de typeStream<Other>
. Si sa valeur d'entrée est présente, il obtient la valeur, qui est l'élément uniqueStream<Other>
. Sinon (si la valeur d'entrée est absente), elle renvoie un videStream<Other>
. Le résultat est donc correct, le même que le code conditionnel d'origine.Dans les commentaires sur ma réponse, concernant le montage rejeté, j'avais décrit cette technique comme "plus concise mais aussi plus obscure". Je m'en tiens à cela. Il m'a fallu un certain temps pour comprendre ce qu'il faisait, et il m'a également fallu un certain temps pour rédiger la description ci-dessus de ce qu'il faisait. La subtilité clé est la transformation de
Optional<Other>
àOptional<Stream<Other>>
. Une fois que vous avez réfléchi, cela a du sens, mais ce n'était pas évident pour moi.Je reconnais cependant que les choses initialement obscures peuvent devenir idiomatiques avec le temps. Il se peut que cette technique finisse par être le meilleur moyen dans la pratique, au moins jusqu'à ce qu'elle
Optional.stream
soit ajoutée (si jamais elle le fait).MISE À JOUR:
Optional.stream
a été ajouté à JDK 9.la source
Vous ne pouvez pas le faire plus concis comme vous le faites déjà.
Vous prétendez que vous ne voulez pas
.filter(Optional::isPresent)
et.map(Optional::get)
.Cela a été résolu par la méthode décrite par @StuartMarks, mais en conséquence, vous le mappez maintenant à un
Optional<T>
, donc vous devez maintenant utiliser.flatMap(this::streamopt)
et unget()
à la fin.Il se compose donc toujours de deux instructions et vous pouvez désormais obtenir des exceptions avec la nouvelle méthode! Parce que, que se passe-t-il si chaque option est vide? Ensuite, le
findFirst()
retournera une option vide et votreget()
échouera!Donc ce que vous avez:
est en fait le meilleur moyen d'accomplir ce que vous voulez, c'est-à-dire que vous souhaitez enregistrer le résultat en tant que
T
, et non en tant queOptional<T>
.Je pris la liberté de créer une
CustomOptional<T>
classe qui enveloppe leOptional<T>
et fournit une méthode supplémentaire,flatStream()
. Notez que vous ne pouvez pas étendreOptional<T>
:Vous verrez que j'ai ajouté
flatStream()
, comme ici:Utilisé comme:
Vous devrez toujours renvoyer un
Stream<T>
ici, car vous ne pouvez pas retournerT
, car si!optional.isPresent()
, alorsT == null
si vous le déclarez tel, mais alors vous.flatMap(CustomOptional::flatStream)
tenteriez d'ajouternull
à un flux et ce n'est pas possible.Comme exemple:
Utilisé comme:
Va maintenant jeter
NullPointerException
à l'intérieur des opérations de flux.Conclusion
La méthode que vous avez utilisée est en fait la meilleure méthode.
la source
Une version légèrement plus courte utilisant
reduce
:Vous pouvez également déplacer la fonction de réduction vers une méthode utilitaire statique, puis elle devient:
la source
Comme ma réponse précédente ne semblait pas être très populaire, je vais essayer à nouveau.
Une réponse courte:
Vous êtes surtout sur la bonne voie. Le code le plus court pour arriver à la sortie souhaitée que j'ai pu trouver est le suivant:
Cela s'adaptera à toutes vos exigences:
Optional<Result>
this::resolve
paresseusement au besointhis::resolve
ne sera pas appelé après le premier résultat non videOptional<Result>
Réponse plus longue
La seule modification par rapport à la version initiale OP était que j'ai supprimé
.map(Optional::get)
avant l'appel.findFirst()
et ajouté.flatMap(o -> o)
comme dernier appel de la chaîne.Cela a un bel effet de se débarrasser du double-Facultatif, chaque fois que stream trouve un résultat réel.
Vous ne pouvez pas vraiment aller plus court que cela en Java.
L'extrait de code alternatif utilisant la
for
technique de boucle plus conventionnelle va être à peu près le même nombre de lignes de code et avoir plus ou moins le même ordre et le nombre d'opérations que vous devez effectuer:this.resolve
,Optional.isPresent
Juste pour prouver que ma solution fonctionne comme annoncé, j'ai écrit un petit programme de test:
(Il a quelques lignes supplémentaires pour déboguer et vérifier que seulement autant d'appels à résoudre que nécessaire ...)
En exécutant cela sur une ligne de commande, j'ai obtenu les résultats suivants:
la source
Si cela ne vous dérange pas d'utiliser une bibliothèque tierce, vous pouvez utiliser Javaslang . C'est comme Scala, mais implémenté en Java.
Il est livré avec une bibliothèque de collection immuable complète qui est très similaire à celle connue de Scala. Ces collections remplacent les collections de Java et le flux de Java 8. Il a également sa propre implémentation d'Option.
Voici une solution pour l'exemple de la question initiale:
Avertissement: je suis le créateur de Javaslang.
la source
Tard à la fête, mais qu'en est-il
Vous pouvez vous débarrasser du dernier get () si vous créez une méthode util pour convertir facultativement en flux manuel:
Si vous renvoyez le flux immédiatement à partir de votre fonction de résolution, vous enregistrez une ligne de plus.
la source
J'aimerais promouvoir les méthodes d'usine pour créer des assistants pour les API fonctionnelles:
La méthode d'usine:
Raisonnement:
Comme avec les références de méthode en général, par rapport aux expressions lambda, vous ne pouvez pas capturer accidentellement une variable de la portée accessible, comme:
t -> streamopt(resolve(o))
Il est composable, vous pouvez par exemple appeler
Function::andThen
le résultat de la méthode d'usine:streamopt(this::resolve).andThen(...)
Alors que dans le cas d'un lambda, vous devez d'abord le lancer:
((Function<T, Stream<R>>) t -> streamopt(resolve(t))).andThen(...)
la source
Null est pris en charge par le flux fourni Ma bibliothèque AbacusUtil . Voici le code:
la source
Si vous êtes bloqué avec Java 8 mais avez accès à Guava 21.0 ou plus récent, vous pouvez utiliser
Streams.stream
pour convertir une option en un flux.Ainsi, étant donné
tu peux écrire
la source
Que dire de cela?
https://stackoverflow.com/a/58281000/3477539
la source
return list.stream().filter(Optional::isPresent).map(Optional::get).collect(Collectors.toList()))
, tout comme la question (et votre réponse liée) a ....get()
sansisPresent()
, alors vous obtenez un avertissement dans IntelliJVous vous trompez très probablement.
Java 8 facultatif n'est pas destiné à être utilisé de cette manière. Il est généralement réservé aux opérations de flux de terminal qui peuvent ou non renvoyer une valeur, comme find par exemple.
Dans votre cas, il peut être préférable d'essayer d'abord de trouver un moyen bon marché de filtrer les éléments qui peuvent être résolus, puis d'obtenir le premier élément en option et de le résoudre comme une dernière opération. Mieux encore - au lieu de filtrer, trouvez le premier élément résoluble et résolvez-le.
La règle générale est que vous devez vous efforcer de réduire le nombre d'éléments dans le flux avant de les transformer en autre chose. YMMV bien sûr.
la source
Optional<Result> searchFor(Term t)
. Cela semble correspondre à l'intention de l'option. De plus, les stream () doivent être évalués paresseusement, donc aucun travail supplémentaire de résolution des termes après le premier correspondant ne doit se produire.