Pourquoi devrais-je utiliser des «opérations fonctionnelles» au lieu d'une boucle for?


39
for (Canvas canvas : list) {
}

NetBeans me suggère d’utiliser des "opérations fonctionnelles":

list.stream().forEach((canvas) -> {
});

Mais pourquoi est-ce préféré ? Au contraire, il est plus difficile à lire et à comprendre. Vous appelez stream()puis forEach()utilisez une expression lambda avec paramètre canvas. Je ne vois pas en quoi cela est plus agréable que la forboucle du premier extrait.

Évidemment, je parle uniquement d'esthétique. Peut-être me manque-t-il un avantage technique. Qu'Est-ce que c'est? Pourquoi devrais-je utiliser la deuxième méthode à la place?



15
Dans votre exemple particulier, cela ne serait pas préféré.
Robert Harvey

1
Tant que la seule opération est unique, chaque fois, j'ai tendance à être d'accord avec vous. Dès que vous ajoutez d'autres opérations au pipeline ou que vous produisez une séquence de sortie, l'approche par flux devient préférable.
JacquesB

@ RobertHarvey serait-ce pas? pourquoi pas?
Sara

@RobertHarvey je conviens que la réponse acceptée montre vraiment comment la version-for est éjectée de l'eau pour les cas plus compliqués, mais je ne vois pas pourquoi pour "gagne" dans le cas trivial. vous dites que cela va de soi, mais je ne le vois pas, alors j'ai demandé.
Sara

Réponses:


45

Les flux fournissent une bien meilleure abstraction pour la composition des différentes opérations que vous souhaitez effectuer en plus des collections ou des flux de données entrants. Surtout lorsque vous devez mapper, filtrer et convertir des éléments.

Votre exemple n'est pas très pratique. Considérez le code suivant du site Oracle .

List<Transaction> groceryTransactions = new Arraylist<>();
for(Transaction t: transactions){
  if(t.getType() == Transaction.GROCERY){
    groceryTransactions.add(t);
  }
}
Collections.sort(groceryTransactions, new Comparator(){
  public int compare(Transaction t1, Transaction t2){
    return t2.getValue().compareTo(t1.getValue());
  }
});
List<Integer> transactionIds = new ArrayList<>();
for(Transaction t: groceryTransactions){
  transactionsIds.add(t.getId());
}

peut être écrit en utilisant des flux:

List<Integer> transactionsIds = 
    transactions.stream()
                .filter(t -> t.getType() == Transaction.GROCERY)
                .sorted(comparing(Transaction::getValue).reversed())
                .map(Transaction::getId)
                .collect(toList());

La deuxième option est beaucoup plus lisible. Ainsi, lorsque vous avez des boucles imbriquées ou diverses boucles effectuant un traitement partiel, c'est un très bon candidat pour l'utilisation de l'API Streams / Lambda.


5
Il existe des techniques d'optimisation telles que la fusion de carte ( stream.map(f).map(g)stream.map(f.andThen(g))) build / réduire la fusion (lors de la construction d' un ruisseau dans une méthode et passer ensuite à une autre méthode qui consomme, le compilateur peut éliminer le flux) et fusion courant ( ce qui peut fusionner plusieurs opérations de flux ensemble en une seule boucle impérative), ce qui peut rendre les opérations de flux beaucoup plus efficaces. Ils sont implémentés dans le compilateur GHC Haskell, ainsi que dans d'autres compilateurs Haskell et d'autres langages fonctionnels, et il existe des implémentations de recherche expérimentale pour Scala.
Jörg W Mittag le

La performance n’est probablement pas un facteur à prendre en compte lorsqu’on considère une boucle fonctionnelle vs car le compilateur pourrait / devrait faire la conversion d’une boucle for en une opération fonctionnelle si Netbeans le peut et qu’il est déterminé qu’il s’agit du chemin optimal.
Ryan

Je ne suis pas d'accord pour dire que le second est plus lisible. Il faut un certain temps pour comprendre ce qui se passe. Existe-t-il un avantage en termes de performances lors de la deuxième méthode car sinon je ne la vois pas?
Bok McDonagh le

@BokMcDonagh, moi, l'expérience est qu'il est moins lisible pour les développeurs qui ne se sont pas donné la peine de se familiariser avec les nouvelles abstractions. Je suggérerais d'utiliser davantage ces API, de se familiariser davantage, car c'est l'avenir. Pas seulement dans le monde Java.
luboskrnac le

16

L’utilisation de l’API de streaming fonctionnelle présente également l’avantage de cacher des détails d’implémentation. Il décrit uniquement ce qui doit être fait, pas comment. Cet avantage devient évident lorsque l’on examine le changement à effectuer, qui consiste à passer de l’exécution de code unique à l’exécution de code parallèle. Il suffit de changer le .stream()à .parallelStream().


13

Au contraire, il est plus difficile à lire et à comprendre.

C'est très subjectif. Je trouve la deuxième version beaucoup plus facile à lire et à comprendre. Cela correspond à la façon dont les autres langages (ex. Ruby, Smalltalk, Clojure, Io, Ioke, Seph) le fait, il nécessite moins de concepts à comprendre (c'est juste un appel de méthode normal comme un autre, alors que le premier exemple est une syntaxe spécialisée).

Si quelque chose, c'est une question de familiarité.


6
Ouais c'est vrai. Mais cela ressemble plus à un commentaire qu'à une réponse.
Omega

L'opération fonctionnelle utilise aussi une syntaxe spécialisée: le "->" est nouveau
Ryan
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.