web-dev-qa-db-fra.com

Dans Java les flux ne sont vraiment que pour le débogage?

Je lis sur les flux Java et découvre de nouvelles choses au fur et à mesure. Une des nouvelles choses que j'ai trouvées est la fonction peek()]. Presque tout ce que j'ai lu on peek indique qu'il devrait être utilisé pour déboguer vos flux.

Que se passe-t-il si j'avais un flux où chaque compte possède un nom d'utilisateur, un champ de mot de passe et une méthode de connexion () et logsIn ()?.

J'ai aussi

Consumer<Account> login = account -> account.login();

et

Predicate<Account> loggedIn = account -> account.loggedIn();

Pourquoi cela serait-il 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 correspond exactement à ce que l'on entend faire. Il;

  • Prend une liste de comptes
  • Essaie de se connecter à chaque compte
  • Filtre tout compte qui n'est pas connecté
  • 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 celui-ci utilisait la méthode .filter () comme suit;

.filter(account -> {
        account.login();
        return account.loggedIn();
    })
105
Adam.J

La clé à retenir de ceci:

N'utilisez pas l'API de manière non intentionnelle, même si cela permet d'atteindre votre objectif immédiat. Cette approche risque de ne plus être efficace, mais elle est également peu claire pour les futurs responsables.


Il n'y a pas de mal à diviser cela en plusieurs opérations, car ce sont des opérations distinctes. Il y a mal à utiliser l'API de manière peu claire et imprévue, ce qui peut avoir des conséquences si ce comportement particulier est modifié dans les futures versions de Java.

L'utilisation de forEach sur cette opération indiquerait clairement au responsable qu'il existe un effet secondaire prévu sur chaque élément de accounts, et que vous effectuez une opération qui peut le muter.

C'est également plus conventionnel en ce sens que peek est une opération intermédiaire qui ne fonctionne pas sur l'ensemble de la collection tant que l'opération du terminal n'est pas exécutée, mais forEach est bien une opération de terminal. De cette façon, vous pouvez argumenter fortement sur le comportement et le flux de votre code, par opposition à poser des questions pour savoir si peek se comporterait de la même façon que forEach dans ce contexte.

accounts.forEach(a -> a.login());
List<Account> loggedInAccounts = accounts.stream()
                                         .filter(Account::loggedIn)
                                         .collect(Collectors.toList());
65
Makoto

La chose importante à comprendre est que les flux sont générés par l’opération de terminal . Le fonctionnement du terminal détermine si tous les éléments doivent être traités ou pas du tout. Donc, collect est une opération qui traite chaque élément, alors que findAny peut arrêter le traitement des éléments une fois qu'il a rencontré un élément correspondant.

Et count() ne peut traiter aucun élément s'il peut déterminer la taille du flux sans traiter les éléments. Comme il s’agit d’une optimisation qui n’a pas été réalisée dans Java 8, mais dans Java 9, il peut être surprenant de passer à Java 9 et d’avoir du code reposant sur count() traitant tous les éléments. Ceci est également lié à d'autres détails dépendants de la mise en œuvre, par exemple. même en Java 9, l’implémentation de référence ne pourra pas prédire la taille d’une source de flux infinie combinée à limit tant qu’aucune limitation fondamentale n’empêche une telle prédiction.

Depuis 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 ne commande pas le traitement des éléments mais effectue l'action en fonction des besoins de l'opération du terminal. Cela implique que vous devez l'utiliser avec le plus grand soin si vous avez besoin d'un traitement particulier, par exemple. vouloir appliquer une action sur tous les éléments. Cela fonctionne s'il est certain que l'opération de terminal traite tous les éléments, mais même dans ce cas, vous devez vous assurer que le développeur suivant ne modifie pas l'opération de terminal (ou que vous oubliez cet aspect subtil).

En outre, bien que les flux garantissent le maintien de l'ordre de rencontre pour certaines combinaisons d'opérations, même pour les 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'action peek peut être invoquée dans un ordre arbitraire et simultané.

Donc, la chose la plus utile que vous puissiez faire avec 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:

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.

92
Holger

Peut-être une règle de base devrait être que si vous utilisez peek en dehors du scénario "débogage", vous ne devriez le faire que si vous êtes sûr des conditions de filtrage final et intermédiaire. Par exemple:

return list.stream().map(foo->foo.getBar())
                    .peek(bar->bar.publish("HELLO"))
                    .collect(Collectors.toList());

semble être un cas valable où vous voulez, en une opération pour transformer tous les Foos en Bars et les saluer tous.

Semble plus efficace et élégant que quelque chose comme:

List<Bar> bars = list.stream().map(foo->foo.getBar()).collect(Collectors.toList());
bars.forEach(bar->bar.publish("HELLO"));
return bars;

et vous ne finissez pas par itérer une collection deux fois.

18
chimera8

Je dirais que peek offre la possibilité de décentraliser du code pouvant muter des objets de flux ou modifier un état global (basé sur eux), au lieu de tout ranger dans un simple ou une fonction composée passé à une méthode de terminal.

Maintenant, la question pourrait être: devons-nous muter les objets de flux ou changer l’état global à partir de fonctions dans un style fonctionnel Java programmation?

Si la réponse à l'une des 2 questions ci-dessus est oui (ou: dans certains cas, oui), alors peek() est certainement pas uniquement à des fins de débogage, pour les mêmes raison pour laquelle forEach() n'est pas uniquement à des fins de débogage.

Pour moi, lorsque je choisis entre forEach() et peek(), je choisis les éléments suivants: Est-ce que je veux que les morceaux de code qui muent les objets de flux soient liés à un objet composable, ou veux-ils qu’ils s’attachent directement à courant?

Je pense que peek() sera mieux couplé avec les méthodes Java9. par exemple. takeWhile() devra peut-être décider quand arrêter l'itération en fonction d'un objet déjà muté. Par conséquent, le comparer à forEach() n'aurait pas le même effet.

P.S. Je n'ai référencé map() nulle part parce que dans le cas où nous souhaitons muter des objets (ou un état global), plutôt que de générer de nouveaux objets, cela fonctionne exactement comme peek().

4
Marinos An

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 être la solution la plus propre.

Semblable à votre cas d'utilisation, supposons que vous souhaitiez filtrer uniquement les comptes actifs, puis vous connecter à ces comptes.

accounts.stream()
    .filter(Account::isActive)
    .peek(login)
    .collect(Collectors.toList());

Peek est utile pour éviter les appels redondants sans avoir à itérer deux fois la collection:

accounts.stream()
    .filter(Account::isActive)
    .map(account -> {
        account.login();
        return account;
    })
    .collect(Collectors.toList());
2
UltimaWeapon

La solution fonctionnelle est de rendre le compte objet immuable. Donc account.login () doit retourner un nouvel objet de compte. Cela signifie que l'opération de la carte peut être utilisée pour la connexion au lieu de peek.

0
Solubris