Pourquoi Iterable ne fournit-il pas les méthodes stream () et parallelStream ()?

Je me demande pourquoi l’interface Iterable ne fournit pas les méthodes stream() et parallelStream() . Considérons la classe suivante:

 public class Hand implements Iterable { private final List list = new ArrayList(); private final int capacity; //... @Override public Iterator iterator() { return list.iterator(); } } 

C’est une mise en œuvre d’une main car vous pouvez avoir des cartes en main lorsque vous jouez à un jeu de cartes à collectionner.

Essentiellement, il enveloppe une List , assure une capacité maximale et offre d’autres fonctionnalités utiles. Il vaut mieux l’implémenter directement sous forme de List .

Maintenant, pour des raisons pratiques, j’ai pensé qu’il serait intéressant d’implémenter Iterable , de telle sorte que vous puissiez utiliser des boucles for-lo améliorées si vous souhaitez effectuer une boucle dessus. (Ma classe Hand fournit également une méthode get(int index) , donc la Iterable est justifiée à mon avis.)

L’interface Iterable fournit les éléments suivants (sans javadoc):

 public interface Iterable { Iterator iterator(); default void forEach(Consumer action) { Objects.requireNonNull(action); for (T t : this) { action.accept(t); } } default Spliterator spliterator() { return Spliterators.spliteratorUnknownSize(iterator(), 0); } } 

Maintenant, pouvez-vous obtenir un stream avec:

 Stream stream = StreamSupport.stream(hand.spliterator(), false); 

Donc, sur la vraie question:

  • Pourquoi Iterable ne fournit-il pas une méthode par défaut qui implémente stream() et parallelStream() , je ne vois rien qui rendrait cela impossible ou indésirable?

Une question connexe que j’ai trouvée est la suivante: Pourquoi Stream n’implémente-t-il pas Iterable ?
Ce qui est assez curieux pour le suggérer de faire un peu l’inverse.

Ce n’était pas une omission; il y a eu une discussion détaillée sur la liste des EG en juin 2013.

La discussion définitive du groupe d’experts est enracinée dans ce fil .

Même s’il semblait “évident” (même pour le groupe d’experts au départ) que stream() semblait avoir un sens sur Iterable , le fait que Iterable soit si général devenait un problème, car la signature évidente:

 Stream stream() 

ce n’était pas toujours ce que tu allais vouloir. Certaines choses qui étaient Iterable préféreraient que leur méthode de stream retourne un IntStream , par exemple. Mais mettre la méthode stream() haut dans la hiérarchie rendrait cela impossible. Au lieu de cela, nous avons vraiment facilité la spliterator() un Stream partir d’une Iterable , en fournissant une méthode spliterator() . L’implémentation de stream() dans Collection est juste:

 default Stream stream() { return StreamSupport.stream(spliterator(), false); } 

N’importe quel client peut obtenir le stream souhaité à partir d’une Iterable avec:

 Stream s = StreamSupport.stream(iter.spliterator(), false); 

En fin de compte, nous avons conclu que l’ajout de stream() à Iterable serait une erreur.

J’ai fait une enquête dans plusieurs des listes de diffusion du projet lambda et je pense avoir trouvé quelques discussions intéressantes.

Je n’ai pas trouvé d’explication satisfaisante jusqu’à présent. Après avoir lu tout cela, j’ai conclu que c’était juste une omission. Mais vous pouvez voir ici qu’il a été discuté à plusieurs resockets au cours des années lors de la conception de l’API.

Lambda Libs Spec Experts

J’ai trouvé une discussion à ce sujet dans la liste de diffusion Lambda Libs Spec Experts :

Sous Iterable / Iterator.stream (), Sam Pullara a déclaré:

Je travaillais avec Brian pour voir comment la fonctionnalité limite / sous-stream [1] pouvait être implémentée et il a suggéré que la conversion en Iterator était la bonne solution. J’avais pensé à cette solution mais je n’ai trouvé aucun moyen évident de prendre un iterator et de le transformer en stream. Il s’avère que c’est là que vous devez d’abord convertir l’iterator en un séparateur, puis convertir le séparateur en stream. Donc, cela m’amène à revenir sur la question de savoir si nous devrions les suspendre directement ou indirectement aux deux.

Ma suggestion est de l’avoir au moins sur Iterator pour pouvoir passer proprement entre les deux mondes et cela serait aussi facilement identifiable que de devoir faire:

Streams.stream (Spliterators.spliteratorUnknownSize (iterator, Spliterator.ORDERED))

Et puis Brian Goetz a répondu :

Je pense que le sharepoint Sam était qu’il y a beaucoup de cours de bibliothèque qui vous donnent un iterator mais ne vous permettent pas nécessairement d’écrire votre propre séparateur. Donc, tout ce que vous pouvez faire est d’appeler le stream (spliteratorUnknownSize (iterator)). Sam suggère que nous définissions Iterator.stream () pour le faire pour vous.

Je voudrais garder les méthodes stream () et spliterator () comme étant destinées aux auteurs de bibliothèques et aux utilisateurs avancés.

Et ensuite

“Etant donné que l’écriture d’un spliterator est plus facile que l’écriture d’un iterator, je préférerais simplement écrire un spliterator au lieu d’un iterator (Iterator fait 90s :)”

Vous manquez le point, cependant. Il y a des millions de classes là-bas qui vous donnent déjà un iterator. Et beaucoup d’entre eux ne sont pas prêts pour le spliterator.

Discussions précédentes sur la liste de diffusion Lambda

Ce n’est peut-être pas la réponse que vous recherchez mais dans la liste de diffusion du projet Lambda, cela a été brièvement discuté. Cela consortingbue peut-être à favoriser une discussion plus large sur le sujet.

Dans les mots de Brian Goetz sous Streams from Iterable :

Reculer…

Il existe de nombreuses façons de créer un stream. Plus vous avez d’informations sur la description des éléments, plus la bibliothèque de stream peut vous offrir de fonctionnalités et de performances. Dans l’ordre de la plus petite à la plupart des informations, ils sont:

Itérateur

Iterator + taille

Spliterator

Spliterator qui connaît sa taille

Spliterator qui connaît sa taille et sait en outre que tous les sous-groupes connaissent leur taille.

(Certains peuvent être surpris de constater que l’on peut extraire le parallélisme même à partir d’un iterator muet dans les cas où Q (travail par élément) est non sortingvial.)

Si Iterable avait une méthode stream (), il suffirait d’envelopper un iterator avec un spliterator, sans informations de taille. Mais, la plupart des choses qui sont interchangeables ont des informations de taille. Ce qui signifie que nous servons des stream déficients. Ce n’est pas si bon.

Un inconvénient de la pratique de l’API décrite ici par Stephen, consistant à accepter Iterable au lieu de Collection, est que vous forcez les choses à travers un “petit tuyau” et que vous supprimez donc les informations de taille lorsque cela peut être utile. C’est bien si tout ce que vous faites est de le faire, mais si vous voulez en faire plus, il est préférable de conserver toutes les informations que vous souhaitez.

La valeur par défaut fournie par Iterable serait vraiment décevante – elle rejetterait la taille même si la grande majorité des Iterables connaissent cette information.

Contradiction?

Cependant, il semble que la discussion repose sur les modifications apscopes par le Groupe d’experts à la conception initiale de Streams, initialement basée sur des iterators.

Même dans ce cas, il est intéressant de noter que dans une interface comme Collection, la méthode de stream est définie comme suit:

 default Stream stream() { return StreamSupport.stream(spliterator(), false); } 

Ce qui pourrait être exactement le même code utilisé dans l’interface Iterable.

C’est pourquoi j’ai dit que cette réponse n’est probablement pas satisfaisante, mais rest intéressante pour la discussion.

Preuve de Refactoring

En poursuivant l’parsing dans la liste de diffusion, il semble que la méthode splitIterator se trouvait à l’origine dans l’interface Collection et, en 2013, elle a été déplacée vers Iterable.

Tirez splitIterator de Collection à Iterable .

Conclusion / Théories?

Il y a alors des chances que l’absence de la méthode dans Iterable ne soit qu’une omission, car il semblerait qu’ils aient également dû déplacer la méthode de stream lorsqu’ils ont déplacé le splitIterator de Collection à Iterable.

S’il y a d’autres raisons, elles ne sont pas évidentes. Quelqu’un d’autre a d’autres théories?

Si vous connaissez la taille, vous pouvez utiliser java.util.Collection qui fournit la méthode stream() :

 public class Hand extends AbstractCollection { private final List list = new ArrayList<>(); private final int capacity; //... @Override public Iterator iterator() { return list.iterator(); } @Override public int size() { return list.size(); } } 

Et alors:

 new Hand().stream().map(...) 

J’ai rencontré le même problème et j’ai été surpris que mon implémentation Iterable puisse être très facilement étendue à une implémentation AbstractCollection en ajoutant simplement la méthode size() heureusement que j’avais la taille de la collection 🙂

Vous devez également envisager de remplacer le Spliterator spliterator() .