En Java, quels sont les avantages des flux par rapport aux boucles? [fermé]


133

On m'a posé cette question lors d'une interview et je ne suis pas convaincu d'avoir donné la meilleure réponse possible. J'ai mentionné que vous pouvez faire une recherche parallèle et que les valeurs nulles ont été gérées par des moyens dont je ne me souviens pas. Maintenant, je réalise que je pensais aux options. Qu'est-ce que j'oublie ici? Ils prétendent que c'est un code meilleur ou plus concis, mais je ne suis pas sûr d'être d'accord.


Compte tenu de la brièveté de la réponse, il semble que ce n'était pas une question trop large après tout.


S'ils posent cette question lors des entretiens, et c'est clair qu'ils le sont, à quoi la décomposition pourrait-elle servir autre que de compliquer la recherche d'une réponse? Je veux dire, qu'est-ce que tu cherches? Je pourrais décomposer la question et avoir toutes les sous-questions répondues, mais ensuite créer une question parentale avec des liens vers toutes les sous-questions ... cela semble assez idiot. Pendant que nous y sommes, donnez-moi un exemple de question moins large. Je ne connais aucun moyen de ne poser qu'une partie de cette question et d'obtenir toujours une réponse significative. Je pourrais poser exactement la même question d'une manière différente. Par exemple, je pourrais demander "À quoi servent les flux?" ou "Quand est-ce que j'utiliserais un flux au lieu d'une boucle for?" ou "Pourquoi s'embêter avec des flux plutôt qu'avec des boucles for?" Ce sont tous exactement la même question.

... ou est-il considéré comme trop large parce que quelqu'un a donné une réponse très longue en plusieurs points? Franchement, n'importe qui au courant pourrait le faire avec pratiquement n'importe quelle question. Si vous êtes l'un des auteurs de la JVM, par exemple, vous pourriez probablement parler de boucles for toute la journée alors que la plupart d'entre nous ne le pourraient pas.

"Veuillez modifier la question pour la limiter à un problème spécifique avec suffisamment de détails pour identifier une réponse adéquate. Évitez de poser plusieurs questions distinctes à la fois. Consultez la page Comment poser pour obtenir de l'aide pour clarifier cette question."

Comme indiqué ci-dessous, une réponse adéquate a été donnée qui prouve qu'il y en a une et qu'elle est assez facile à fournir.


7
Ceci est basé sur l'opinion à mon humble avis. Personnellement, je préfère les streams car cela rend le code plus lisible. Cela permet d'écrire ce que vous voulez au lieu de comment . De plus, c'est totalement dur de faire des choses incroyables avec des one-liners.
Arnaud Denoyelle

19
Même si c'est une ligne 30, une doublure? Je n'aime pas les longues chaînes.
user447607

1
D'ailleurs, tout ce que je recherche ici, c'est la réponse appropriée pour une interview. C'est la seule «opinion» qui compte.
user447607

1
Sur le plan éducatif, cette question m'a évité une certaine dégradation lors d'une prochaine interview aussi, @slim l'a vraiment cloué, mais sur le plan industriel, elle parle également de la façon dont les langages de programmation Microsoft ont construit leur carrière en arrachant le langage java, et enfin java se venge en déchirant de l' expression Lambda et des flux des adversaires, voyons ce que java va faire à l'avenir à propos des structures et des syndicats :)
ShayHaned

5
Notez que les flux n'utilisent qu'une fraction de la puissance de la programmation fonctionnelle: - /
Thorbjørn Ravn Andersen

Réponses:


252

Il est intéressant que la question de l'entrevue porte sur les avantages, sans poser de questions sur les inconvénients, car il y a les deux.

Les flux sont un style plus déclaratif . Ou un style plus expressif . Il peut être préférable de déclarer votre intention dans le code plutôt que de décrire comment cela se fait:

 return people
     .filter( p -> p.age() < 19)
     .collect(toList());

... dit clairement que vous filtrez les éléments correspondants d'une liste, alors que:

 List<Person> filtered = new ArrayList<>();
 for(Person p : people) {
     if(p.age() < 19) {
         filtered.add(p);
     }
 }
 return filtered;

Dit "je fais une boucle". Le but de la boucle est enfoui plus profondément dans la logique.

Les flux sont souvent lacunaires . Le même exemple le montre. Terser n'est pas toujours meilleur, mais si vous pouvez être concis et expressif en même temps, tant mieux.

Les flux ont une forte affinité avec les fonctions . Java 8 introduit des lambdas et des interfaces fonctionnelles, ce qui ouvre toute une boîte à jouets de techniques puissantes. Les flux offrent le moyen le plus pratique et le plus naturel d'appliquer des fonctions à des séquences d'objets.

Les flux encouragent moins de mutabilité . C'est en quelque sorte lié à l'aspect de la programmation fonctionnelle - le type de programmes que vous écrivez à l'aide de flux ont tendance à être le type de programmes où vous ne modifiez pas les objets.

Les flux encouragent un couplage plus lâche . Votre code de gestion de flux n'a pas besoin de connaître la source du flux ou sa méthode de terminaison éventuelle.

Les flux peuvent exprimer succinctement un comportement assez sophistiqué . Par exemple:

 stream.filter(myfilter).findFirst();

Peut sembler à première vue comme s'il filtre tout le flux, puis renvoie le premier élément. Mais en fait, il findFirst()dirige toute l'opération, de sorte qu'il s'arrête efficacement après avoir trouvé un élément.

Les flux offrent des possibilités de gains d'efficacité futurs . Certaines personnes ont évalué et constaté que les flux à un seul thread à partir de Lists ou de tableaux en mémoire peuvent être plus lents que la boucle équivalente. Ceci est plausible car il y a plus d'objets et de frais généraux en jeu.

Mais les flux évoluent. Outre la prise en charge intégrée de Java pour les opérations de flux parallèles, il existe quelques bibliothèques pour la réduction de carte distribuée utilisant Streams comme API, car le modèle s'adapte.

Désavantages?

Performances : une forboucle à travers un tableau est extrêmement légère en termes d'utilisation du tas et du processeur. Si la vitesse brute et l'économie de la mémoire sont une priorité, l'utilisation d'un flux est pire.

Familiarité Le monde regorge de programmeurs procéduraux expérimentés, issus de nombreux langages, pour qui les boucles sont familières et les flux sont nouveaux. Dans certains environnements, vous souhaitez écrire du code familier à ce type de personne.

Frais généraux cognitifs . En raison de sa nature déclarative et de l'abstraction accrue de ce qui se passe en dessous, vous devrez peut-être créer un nouveau modèle mental de la relation entre le code et l'exécution. En fait, vous n'avez besoin de le faire que lorsque les choses tournent mal, ou si vous avez besoin d'analyser en profondeur les performances ou des bugs subtils. Quand ça "fonctionne juste", ça marche juste.

Les débogueurs s'améliorent, mais même maintenant, lorsque vous parcourez le code de flux dans un débogueur, cela peut être plus difficile que la boucle équivalente, car une boucle simple est très proche des variables et des emplacements de code avec lesquels un débogueur traditionnel fonctionne.


4
Je pense qu'il serait juste de préciser que les éléments de type flux deviennent beaucoup plus courants et apparaissent maintenant dans de nombreux langages couramment utilisés qui ne sont pas spécialement orientés FP.
Casey

5
Compte tenu des avantages et des inconvénients énumérés ici, je pense que les flux ne valent PAS la peine pour autre chose que des utilisations très simples (peu de logique si / alors / sinon, pas beaucoup d'appels imbriqués ou de lambdas, etc.), dans des parties de performance non critiques
Henrik Kjus Alstad

1
@HenrikKjusAlstad Ce n'est absolument pas le plat à emporter que j'avais l'intention de communiquer. Les flux sont matures, puissants, expressifs et parfaitement adaptés au code de production.
slim

Oh, je ne voulais pas dire que je ne l'utiliserais pas en production. Mais plutôt, je choisirais par défaut des boucles / ifs à l'ancienne, etc., plutôt que des flux, surtout si le flux résultant aurait l'air complexe. Je suis sûr qu'il y a des utilisations où un flux battra des boucles et si c'est dans la clarté, mais le plus souvent, je pense que c'est "lié", ou parfois même l'inverse. Ainsi, je mettrais du poids sur l'argument des frais généraux cognitifs pour s'en tenir aux anciennes méthodes.
Henrik Kjus Alstad

1
@lijepdam - mais vous auriez toujours du code qui dit "Je suis en train d'itérer sur cette liste (voir à l'intérieur de la boucle pour savoir pourquoi)", alors que "itérer sur la liste" n'est pas l'intention principale du code.
slim le

16

Mis à part le plaisir syntaxique, les Streams sont conçus pour fonctionner avec des ensembles de données potentiellement infiniment volumineux, tandis que les tableaux, les collections et presque toutes les classes Java SE qui implémentent Iterable sont entièrement en mémoire.

Un inconvénient d'un Stream est que les filtres, les mappages, etc., ne peuvent pas lever d'exceptions vérifiées. Cela fait d'un Stream un mauvais choix pour, par exemple, les opérations d'E / S intermédiaires.


7
Bien sûr, vous pouvez également parcourir des sources infinies.
slim

Mais si les éléments à traiter sont persistants dans une base de données, comment utilisez-vous Streams? Un développeur junior pourrait être tenté de les lire tous dans une collection juste pour utiliser Streams. Et ce serait un désastre.
Lluis Martinez

2
@LluisMartinez une bonne bibliothèque client DB renverra quelque chose comme Stream<Row>- ou il serait possible d'écrire sa propre Streamimplémentation en enveloppant les opérations de curseur de résultat DB.
slim

Que Streams ignore silencieusement les exceptions est un bogue qui m'a récemment mordu. Pas intuitif.
xxfelixxx

@xxfelixxx Streams n'ignore pas silencieusement les exceptions. Essayez d'exécuter ceci:Arrays.asList("test", null).stream().forEach(s -> System.out.println(s.length()));
VGR

8
  1. Vous avez mal compris: les opérations parallèles utilisent Streams, pas Optionals.

  2. Vous pouvez définir des méthodes fonctionnant avec des flux: les prendre comme paramètres, les renvoyer, etc. Vous ne pouvez pas définir une méthode qui prend une boucle comme paramètre. Cela permet une opération de flux compliquée une fois et de l'utiliser plusieurs fois. Notez que Java a ici un inconvénient: vos méthodes doivent être appelées plutôt que someMethod(stream)celles de stream stream.someMethod(), donc les mélanger complique la lecture: essayez de voir l'ordre des opérations dans

    myMethod2(myMethod(stream.transform(...)).filter(...))

    De nombreux autres langages (C #, Kotlin, Scala, etc.) autorisent une certaine forme de «méthodes d'extension».

  3. Même lorsque vous n'avez besoin que d'opérations séquentielles et que vous ne voulez pas les réutiliser, afin de pouvoir utiliser des flux ou des boucles, des opérations simples sur les flux peuvent correspondre à des changements assez complexes dans les boucles.


Expliquez 1. L'interface facultative n'est-elle pas le moyen par lequel les valeurs nulles sont gérées en chaîne? Concernant 3, cela a du sens car avec des filtres court-circuités, la méthode ne sera invoquée que pour des occurrences spécifiées. Efficace. Il est logique que je puisse dire que leur utilisation réduit le besoin d'écrire du code supplémentaire qui devra être testé, etc. Après examen, je ne suis pas sûr de ce que vous entendez par le cas séquentiel dans 2.
user447607

1. Optionalest une alternative à null, mais cela n'a rien à voir avec les opérations parallèles. À moins que "maintenant je réalise que je pensais aux options" dans votre question ne parle que de la nullmanipulation?
Alexey Romanov

J'ai changé l'ordre de 2 et 3 et les ai un peu élargis.
Alexey Romanov

6

Vous bouclez sur une séquence (tableau, collection, entrée, ...) parce que vous voulez appliquer une fonction aux éléments de la séquence.

Les flux vous permettent de composer des fonctions sur des éléments de séquence et permettent d' implémenter les fonctions les plus courantes (ex: mappage, filtrage, recherche, tri, collecte, ...) indépendamment d'un cas concret.

Par conséquent, étant donné une tâche de bouclage dans la plupart des cas, vous pouvez l'exprimer avec moins de code en utilisant Streams, c'est-à-dire que vous gagnez en lisibilité .


4
Eh bien, ce n'est pas seulement la lisibilité. Le code que vous n'avez pas à écrire est du code que vous n'avez pas à tester.
user447607

3
semble que vous proposez également de bonnes réponses pour votre entretien
wero

6

Je dirais sa parallélisation qui est si facile à utiliser. Essayez d'itérer sur des millions d'entrées en parallèle avec une boucle for. Nous allons vers de nombreux processeurs, pas plus vite; donc plus il est facile de courir en parallèle, mieux c'est, et avec Streams c'est un jeu d'enfant.

Ce que j'aime beaucoup, c'est la verbosité qu'ils offrent. Il faut peu de temps pour comprendre ce qu'ils font et produisent réellement par opposition à la façon dont ils le font.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.