Je lis des informations sur les flux Java et je découvre de nouvelles choses au fur et à mesure. L'une des nouveautés que j'ai trouvées était la peek()
fonction. Presque tout ce que j'ai lu sur peek dit qu'il devrait être utilisé pour déboguer vos Streams.
Et si j'avais un Stream où chaque compte a un nom d'utilisateur, un champ de mot de passe et une méthode login () et logIn ().
j'ai aussi
Consumer<Account> login = account -> account.login();
et
Predicate<Account> loggedIn = account -> account.loggedIn();
Pourquoi serait-ce si mauvais?
List<Account> accounts; //assume it's been setup
List<Account> loggedInAccount =
accounts.stream()
.peek(login)
.filter(loggedIn)
.collect(Collectors.toList());
Pour autant que je sache, cela fait exactement ce qu'il est prévu de faire. Il;
- Prend une liste de comptes
- Tente de se connecter à chaque compte
- Filtre tous les comptes non connectés
- Collecte les comptes connectés dans une nouvelle liste
Quel est l'inconvénient de faire quelque chose comme ça? Une raison pour laquelle je ne devrais pas continuer? Enfin, sinon cette solution alors quoi?
La version originale de ceci utilisait la méthode .filter () comme suit;
.filter(account -> {
account.login();
return account.loggedIn();
})
java
java-8
java-stream
peek
Adam.J
la source
la source
forEach
peut-être l'opération que vous souhaitez par opposition àpeek
. Ce n'est pas parce que c'est dans l'API qu'il n'est pas ouvert aux abus (commeOptional.of
)..peek(Account::login)
et.filter(Account::loggedIn)
; il n'y a aucune raison d'écrire un consommateur et un prédicat qui appelle simplement une autre méthode comme celle-là.forEach()
etpeek()
, ne peuvent fonctionner que via des effets secondaires; ceux-ci doivent être utilisés avec précaution. ». Ma remarque était plutôt de rappeler que l'peek
opération (qui est conçue à des fins de débogage) ne doit pas être remplacée en faisant la même chose dans une autre opération commemap()
oufilter()
.Réponses:
Les principaux points à retenir:
N'utilisez pas l'API de manière involontaire, même si elle atteint votre objectif immédiat. Cette approche peut se rompre à l'avenir, et elle n'est pas non plus claire pour les futurs responsables.
Il n'y a aucun mal à diviser cela en plusieurs opérations, car ce sont des opérations distinctes. L' utilisation de l'API de manière peu claire et involontaire est nuisible, ce qui peut avoir des ramifications si ce comportement particulier est modifié dans les futures versions de Java.
L'utilisation
forEach
de cette opération indiquerait clairement au mainteneur qu'il existe un effet secondaire prévu sur chaque élément deaccounts
, et que vous effectuez une opération qui peut le muter.C'est également plus conventionnel dans le sens où il
peek
s'agit d'une opération intermédiaire qui n'opère pas sur l'ensemble de la collection tant que l'opération de terminal n'est pas exécutée, mais quiforEach
est en fait une opération de terminal. De cette façon, vous pouvez faire des arguments forts autour du comportement et du flux de votre code plutôt que de poser des questions surpeek
le comportement de celui-forEach
ci dans ce contexte.la source
forEach
directement à la collection source:accounts.forEach(a -> a.login());
login()
méthode retournait uneboolean
valeur indiquant le statut de réussite…login()
retourne aboolean
, vous pouvez l'utiliser comme prédicat qui est la solution la plus propre. Cela a toujours un effet secondaire, mais c'est correct tant que cela n'interfère pas, c'est-à-dire que lelogin
processus de l'unAccount
n'a aucune influence sur le processus de connexion d'un autreAccount
.La chose importante que vous devez comprendre est que les flux sont pilotés par le fonctionnement du terminal . L'opération de terminal détermine si tous les éléments doivent être traités ou pas du tout. Il en
collect
va de même pour une opération qui traite chaque élément, alors qu'ellefindAny
peut arrêter le traitement des éléments une fois qu'elle a rencontré un élément correspondant.Et
count()
ne peut traiter aucun élément du tout lorsqu'il peut déterminer la taille du flux sans traiter les éléments. Puisqu'il s'agit d'une optimisation non faite en Java 8, mais qui le sera en Java 9, il peut y avoir des surprises lorsque vous passez à Java 9 et que vous avez du code reposant sur lecount()
traitement de tous les éléments. Ceci est également lié à d'autres détails dépendants de l'implémentation, par exemple même dans Java 9, l'implémentation de référence ne sera pas capable de prédire la taille d'une source de flux infinie combinée aveclimit
alors qu'il n'y a pas de limitation fondamentale empêchant une telle prédiction.Puisque
peek
permet «d'effectuer l'action fournie sur chaque élément au fur et à mesure que les éléments sont consommés à partir du flux résultant », il n'impose pas le traitement des éléments mais exécutera l'action en fonction des besoins de l'opération du terminal. Cela implique que vous devez l'utiliser avec beaucoup de précaution si vous avez besoin d'un traitement particulier, par exemple si vous voulez appliquer une action sur tous les éléments. Cela fonctionne si le fonctionnement du terminal est garanti pour traiter tous les éléments, mais même dans ce cas, vous devez être sûr que le développeur suivant ne modifie pas le fonctionnement du terminal (ou vous oubliez cet aspect subtil).En outre, alors que les flux garantissent le maintien de l'ordre de rencontre pour une certaine combinaison d'opérations même pour des flux parallèles, ces garanties ne s'appliquent pas à
peek
. Lors de la collecte dans une liste, la liste résultante aura le bon ordre pour les flux parallèles ordonnés, mais l'peek
action peut être invoquée dans un ordre arbitraire et simultanément.Ainsi, la chose la plus utile que vous puissiez faire
peek
est de savoir si un élément de flux a été traité, ce qui est exactement ce que dit la documentation de l'API:la source
Peut-être qu'une règle de base devrait être que si vous utilisez un coup d'oeil en dehors du scénario de «débogage», vous ne devriez le faire que si vous êtes sûr des conditions de filtrage de fin et intermédiaires. Par exemple:
semble être un cas valable où vous voulez, en une seule opération, transformer tous les Foos en Bars et leur dire à tous bonjour.
Semble plus efficace et élégant que quelque chose comme:
et vous ne finissez pas par itérer une collection deux fois.
la source
Je dirais que cela
peek
offre la possibilité de décentraliser le code qui peut muter des objets de flux, ou modifier l'état global (en fonction d'eux), au lieu de tout mettre dans une fonction simple ou composée passée à une méthode de terminal.Maintenant, la question pourrait être: devrions-nous muter les objets de flux ou changer l'état global à partir des fonctions dans la programmation Java de style fonctionnel ?
Si la réponse à l'une des 2 questions ci-dessus est oui (ou: dans certains cas oui), ce
peek()
n'est certainement pas uniquement à des fins de débogage , pour la même raison quiforEach()
n'est pas uniquement à des fins de débogage .Pour moi, lorsque je choisis entre
forEach()
etpeek()
, je choisis ce qui suit: Est-ce que je veux que des morceaux de code qui mutent des objets de flux soient attachés à un composable, ou est-ce que je veux qu'ils s'attachent directement au flux?Je pense qu'il
peek()
sera mieux associé aux méthodes java9. par exemple, iltakeWhile()
peut être nécessaire de décider quand arrêter l'itération en se basant sur un objet déjà muté, de sorte que le coupler avecforEach()
n'aurait pas le même effet.PS je ne l'ai référencé
map()
nulle part car au cas où on voudrait muter des objets (ou état global), plutôt que de générer de nouveaux objets, ça fonctionne exactement commepeek()
.la source
Bien que je sois d'accord avec la plupart des réponses ci-dessus, j'ai un cas dans lequel l'utilisation de peek semble en fait la façon la plus propre de procéder.
Similaire à votre cas d'utilisation, supposons que vous souhaitiez filtrer uniquement sur les comptes actifs, puis effectuer une connexion sur ces comptes.
Peek est utile pour éviter l'appel redondant sans avoir à itérer la collection deux fois:
la source
La solution fonctionnelle consiste à rendre l'objet compte immuable. Donc account.login () doit retourner un nouvel objet de compte. Cela signifie que l'opération de carte peut être utilisée pour la connexion au lieu de peek.
la source