Est-ce un anti-modèle d'utiliser peek () pour modifier un élément de flux?

37

Supposons que j'ai un flux de choses et que je veuille les "enrichir" à mi-parcours, je peux utiliser peek()ceci, par exemple:

streamOfThings.peek(this::thingMutator).forEach(this::someConsumer);

Supposons que la mutation des objets à ce stade du code constitue un comportement correct. Par exemple, la thingMutatorméthode peut définir le champ "lastProcessed" à l'heure actuelle.

Cependant, peek()dans la plupart des contextes, cela signifie "regarde, mais ne touche pas".

L'utilisation peek()de la mutation d' éléments de flux est-elle un anti-modèle ou une mauvaise idée?

Modifier:

L’approche alternative, plus conventionnelle, consisterait à convertir le consommateur:

private void thingMutator(Thing thing) {
    thing.setLastProcessed(System.currentTimeMillis());
}

à une fonction qui retourne le paramètre:

private Thing thingMutator(Thing thing) {
    thing.setLastProcessed(currentTimeMillis());
    return thing;
}

et utilisez map()plutôt:

stream.map(this::thingMutator)...

Mais cela introduit un code superficiel (le return) et je ne suis pas convaincu que ce soit plus clair, car vous savez qu'il peek()retourne le même objet, mais map()il n'est même pas évident en un coup d'œil qu'il s'agisse de la même classe d'objet.

De plus, avec peek()vous , vous pouvez avoir un lambda qui mute, mais avec map()vous, vous devez construire une épave de train. Comparer:

stream.peek(t -> t.setLastProcessed(currentTimeMillis())).forEach(...)
stream.map(t -> {t.setLastProcessed(currentTimeMillis()); return t;}).forEach(...)

Je pense que la peek()version est plus claire et que le lambda est en train de muter, il n’ya donc pas d’effet secondaire "mystérieux". De même, si une référence de méthode est utilisée et que le nom de la méthode implique clairement une mutation, cela aussi est clair et évident.

Sur une note personnelle, je n'hésite pas à utiliser peek()pour muter - je trouve cela très pratique.

bohémien
la source
1
Avez-vous utilisé peekavec un flux qui génère ses éléments de manière dynamique? Cela fonctionne-t-il toujours ou les modifications sont-elles perdues? La modification des éléments d'un flux ne me semble pas fiable.
Sebastian Redl
1
@SebastianRedl Je l'ai trouvé fiable à 100%. Je l'ai d'abord utilisé pour collecter pendant le traitement: List<Thing> list; things.stream().peek(list::add).forEach(...);très pratique. Dernièrement. Je l' ai utilisé pour ajouter des informations pour la publication: Map<Thing, Long> timestamps = ...; return things.stream().peek(t -> t.setTimestamp(timestamp.get(t))).collect(toList());. Je sais qu'il existe d'autres façons de faire cet exemple, mais je simplifie énormément ici. Utilisation peek()produit un code IMHO plus compact et plus élégant. La lisibilité mise à part, cette question concerne vraiment ce que vous avez soulevé; est-ce sûr / fiable?
Bohême
2
La question Dans les flux Java est vraiment juste pour le débogage? peut être intéressant aussi.
Vincent Savard
@Bohémien. Pratiquez-vous toujours cette approche avec peek? J'ai une question similaire sur stackoverflow et j'espère que vous pourrez l'examiner et donner votre avis. Merci. stackoverflow.com/questions/47356992/…
tsolakp

Réponses:

23

Vous avez raison, "peek" au sens anglais du mot signifie "regardez, mais ne touchez pas".

Cependant, le JavaDoc indique:

coup d'oeil

Stream peek (Action du consommateur)

Renvoie un flux composé des éléments de ce flux, en effectuant en outre l'action fournie sur chaque élément à mesure que les éléments sont consommés à partir du flux résultant.

Mots clés: "effectuer ... une action" et "consommé". Le JavaDoc est très clair que nous devrions nous attendre peekà avoir la possibilité de modifier le flux.

Cependant, le JavaDoc indique également:

Note API:

Cette méthode existe principalement pour prendre en charge le débogage, où vous souhaitez voir les éléments lorsqu'ils passent au-delà d'un certain point dans un pipeline.

Cela indique qu'il est davantage destiné à l'observation, par exemple en enregistrant des éléments dans le flux.

Ce que je retiens de tout cela, c'est que nous pouvons effectuer des actions en utilisant les éléments du flux, tout en évitant de les transformer en mutation . Par exemple, continuez et appelez les méthodes sur les objets, mais essayez d'éviter de muter les opérations sur ceux-ci.

À tout le moins, je voudrais ajouter un bref commentaire à votre code dans ce sens:

// Note: this peek() call will modify the Things in the stream.
streamOfThings.peek(this::thingMutator).forEach(this::someConsumer);

Les avis diffèrent quant à l'utilité de tels commentaires, mais je les utiliserais dans ce cas.


la source
1
Pourquoi avez-vous besoin d'un commentaire si le nom de la méthode indique clairement une mutation, par exemple thingMutator, ou plus concrète, resetLastProcessedetc. À moins d'une raison impérieuse, les commentaires de nécessité tels que votre suggestion indiquent généralement un choix médiocre de noms de variables et / ou de méthodes. Si de bons noms sont choisis, n'est-ce pas suffisant? Ou êtes-vous en train de dire que malgré une bonne réputation, tout ce qui se passe peek()est comme un angle mort que la plupart des programmeurs «écrémeraient» (sauteraient visuellement)? En outre, "principalement pour le débogage" n'est pas la même chose que "uniquement pour le débogage" - à quels usages autres que "principalement"?
Bohême
@ Bohemian Je l'expliquerais principalement parce que le nom de la méthode n'est pas intuitif. Et je ne travaille pas pour Oracle. Je ne fais pas partie de leur équipe Java. Je n'ai aucune idée de ce qu'ils voulaient.
@ Bohémien parce qu'en cherchant ce qui modifie les éléments d'une façon qui ne me plaît pas, je vais arrêter de lire la ligne à "peek" car "peek" ne change rien. Ce n’est que plus tard, lorsque tous les autres emplacements de code ne contiendraient pas le bogue que je poursuivais, j’y reviendrai, éventuellement armé d’un débogueur et peut-être ensuite aller "omg, qui a mis ce code trompeur là-bas?!"
Frank Hopkins le
@ Bohémien dans l'exemple ici où tout est dans une ligne, il faut lire de toute façon, mais on peut sauter le contenu de "peek" et une déclaration de flux va souvent sur plusieurs lignes avec une opération de flux par ligne
Frank Hopkins
1

Cela pourrait être facilement mal interprété, aussi je voudrais éviter de l'utiliser comme ceci. La meilleure option consiste probablement à utiliser une fonction lambda pour fusionner les deux actions nécessaires dans l'appel forEach. Vous pouvez également envisager de renvoyer un nouvel objet plutôt que de le transformer: il est peut-être légèrement moins efficace, mais il est probable que le code sera plus lisible et réduira le risque d'utilisation accidentelle de la liste modifiée pour une autre opération ont reçu l'original.

Jules
la source
-2

La note de l'API nous indique que la méthode a été ajoutée principalement pour des actions telles que les statistiques de débogage / journalisation / activation, etc.

@apiNote Cette méthode existe principalement pour prendre en charge le débogage, où vous souhaitez * voir les éléments lorsqu'ils dépassent un certain point dans un pipeline: *

       {@code
     * Stream.of ("un", "deux", "trois", "quatre")
     * .filter (e -> e.length ()> 3)
     * .peek (e -> System.out.println ("valeur filtrée:" + e))
     * .map (String :: toUpperCase)
     * .peek (e -> System.out.println ("Valeur mappée:" + e))
     * .collect (Collectors.toList ());
     *}

javaProgrammer
la source
2
Votre réponse est déjà couverte par Snowman's.
Vincent Savard
3
Et "principalement" ne signifie pas "seulement".
Bohême