web-dev-qa-db-fra.com

Pourquoi Iterable <T> 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érez la classe suivante:

public class Hand implements Iterable<Card> {
    private final List<Card> list = new ArrayList<>();
    private final int capacity;

    //...

    @Override
    public Iterator<Card> iterator() {
        return list.iterator();
    }
}

C'est une implémentation d'une main car vous pouvez avoir des cartes dans votre main tout en jouant à un jeu de cartes à collectionner.

Essentiellement, il enveloppe un List<Card>, Assure une capacité maximale et offre quelques fonctionnalités utiles. Il vaut mieux l’appliquer directement en tant que List<Card>.

Maintenant, par commodité, j’ai pensé que ce serait bien d’implémenter Iterable<Card> De telle sorte que vous puissiez utiliser des boucles forelles améliorées si vous voulez le parcourir en boucle. (Ma classe Hand fournit également une méthode get(int index); par conséquent, le Iterable<Card> Est justifié à mon avis.)

L’interface Iterable fournit les éléments suivants (javadoc laissé de côté):

public interface Iterable<T> {
    Iterator<T> iterator();

    default void forEach(Consumer<? super T> action) {
        Objects.requireNonNull(action);
        for (T t : this) {
            action.accept(t);
        }
    }

    default Spliterator<T> spliterator() {
        return Spliterators.spliteratorUnknownSize(iterator(), 0);
    }
}

Maintenant, pouvez-vous obtenir un flux avec:

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

Donc, sur la vraie question:

  • Pourquoi Iterable<T> 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 <T> n'implémente-t-il pas Iterable <T>?
Ce qui est curieusement suffisant pour lui suggérer de le faire un peu à l’inverse.

229
skiwi

Ce n'était pas une omission; une discussion détaillée sur la liste EG a eu lieu en juin 2013.

La discussion finale du groupe d'experts est basée sur ce fil .

Alors qu'il semblait "évident" (même au début pour le groupe d'experts) que stream() semblait avoir du sens sur Iterable, le fait que Iterable soit si général est devenu un problème , parce que la signature évidente:

Stream<T> stream()

n'était pas toujours ce que vous alliez vouloir. Certaines choses qui étaient Iterable<Integer> Préféreraient que leur méthode de flux retourne un IntStream, par exemple. Mais placer la méthode stream() aussi haut dans la hiérarchie rendrait cela impossible. Au lieu de cela, nous avons rendu très facile la création d'un Stream à partir d'un Iterable en fournissant une méthode spliterator(). L'implémentation de stream() dans Collection est juste:

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

N'importe quel client peut obtenir le flux qu'il souhaite d'un Iterable avec:

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

En fin de compte, nous avons conclu qu'ajouter stream() à Iterable serait une erreur.

282
Brian Goetz

J'ai mené une enquête sur plusieurs listes de diffusion lambda du projet 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 reprises au cours des années lors de la conception de l’API.

Experts en spécifications Lambda Libs

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é de limitation/sous-flux [1] pourrait être mise en œuvre et il a suggéré que la conversion en Iterator était la bonne façon de procéder. J'avais pensé à cette solution, mais je n'avais trouvé aucun moyen évident de transformer un itérateur en un flux. Il s'avère que c'est là-dedans, vous devez d'abord convertir l'itérateur en un séparateur puis convertir le séparateur en un flux. Cela m’amène donc à revenir sur la question de savoir si nous devrions suspendre directement l’un des Iterable/Iterator ou les deux.

Ma suggestion est au moins de l'avoir sur Iterator afin que vous puissiez vous déplacer proprement entre les deux mondes et que cela soit facilement découvrable plutôt que d'avoir à le faire:

Streams.stream (Spliterators.spliteratorUnknownSize (itérateur, Spliterator.ORDERED))

Et ensuite Brian Goetz a répond :

Je pense que le point de Sam était qu'il y a beaucoup de classes de bibliothèque qui vous donnent un Iterator mais ne vous laissent pas nécessairement écrire votre propre séparateur. Donc tout ce que vous pouvez faire est d'appeler stream (spliteratorUnknownSize (iterator)). Sam suggère de définir Iterator.stream () pour le faire à votre place.

Je souhaite que les méthodes stream () et spliterator () soient destinées aux auteurs de bibliothèque/utilisateurs avancés.

et plus tard

"Etant donné qu'écrire un Spliterator est plus facile que d'écrire un Iterator, je préférerais juste écrire un Spliterator au lieu d'un Iterator (Iterator est tellement des années 90 :)"

Vous manquez le point, cependant. Il y a des zillions de classes qui déjà vous remettent un Itérateur. Et beaucoup d'entre eux ne sont pas prêts pour le spliterator.

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

Ce n'est peut-être pas la réponse que vous cherchez, mais dans la liste de diffusion Project Lambda cela a été brièvement discuté. Cela peut peut-être aider à 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 flux. Plus vous avez d'informations sur la manière de décrire les éléments, plus la bibliothèque de flux peut vous apporter fonctionnalités et performances. Du moins au plus grand nombre d'informations, ils sont:

Itérateur

Itérateur + taille

Spliterator

Spliterator qui connaît sa taille

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

(Certains pourraient être surpris de constater que nous pouvons extraire le parallélisme même à partir d'un itérateur muet dans les cas où Q (travail par élément) est non trivial.)

Si Iterable avait une méthode stream (), il faudrait simplement envelopper un Iterator avec un Spliterator, sans information de taille. Mais, la plupart des choses qui sont itératives do ont des informations de taille. Ce qui signifie que nous servons des flux déficients. Ce n'est pas si bon.

L'un des inconvénients de la pratique d'API décrite par Stephen ici, consistant à accepter Iterable au lieu de Collection, est que vous forcez les choses à travers un "petit tuyau" et que, par conséquent, vous supprimez les informations de taille lorsque cela peut être utile. C'est bien si tout ce que vous faites est de vous en occuper, mais si vous voulez faire plus, mieux vaut conserver toutes les informations que vous souhaitez.

La valeur par défaut fournie par Iterable serait effectivement une merde - elle ignorerait la taille même si la grande majorité d’Itables connaissait cette information.

Contradiction?

Cependant, il semble que la discussion soit basée sur les modifications apportées par le groupe d'experts à la conception initiale de Streams, qui était initialement basée sur des itérateurs.

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

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

Quel pourrait être exactement le même code utilisé dans l'interface Iterable.

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

Preuve de refactoring

Poursuivant l'analyse dans la liste de diffusion, il semble que la méthode splitIterator était à l'origine dans l'interface Collection, et à un moment donné en 2013, ils l'ont déplacée vers Iterable.

Tirez splitIterator de Collection à Iterable .

Conclusion/Théories?

Il est donc probable que l'absence de la méthode dans Iterable soit simplement une omission, car il semblerait qu'ils auraient également dû déplacer la méthode de flux quand ils ont déplacé le splitIterator de Collection à Iterable.

S'il existe d'autres raisons, celles-ci ne sont pas évidentes. Quelqu'un d'autre a d'autres théories?

23
Edwin Dalorzo

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

public class Hand extends AbstractCollection<Card> {
   private final List<Card> list = new ArrayList<>();
   private final int capacity;

   //...

   @Override
   public Iterator<Card> iterator() {
       return list.iterator();
   }

   @Override
   public int size() {
      return list.size();
   }
}

Puis:

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

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

Vous devriez également envisager de remplacer Spliterator<E> spliterator().

6
Udo