web-dev-qa-db-fra.com

Pourquoi Stream <T> n'implémente-t-il pas Iterable <T>?

En Java 8, nous avons la classe Stream <T> , qui possède curieusement une méthode

Iterator<T> iterator()

Vous vous attendez donc à ce qu'il implémente l'interface Iterable <T> , ce qui nécessite exactement cette méthode, mais ce n'est pas le cas.

Lorsque je veux parcourir un flux à l'aide d'une boucle foreach, je dois faire quelque chose comme:

public static Iterable<T> getIterable(Stream<T> s) {
    return new Iterable<T> {
        @Override
        public Iterator<T> iterator() {
            return s.iterator();
        }
    };
}

for (T element : getIterable(s)) { ... }

Est-ce que j'ai râté quelque chose?

237
roim

Il y a déjà des personnes qui ont demandé la même chose sur la liste de diffusion ☺. La raison principale est qu'itable possède également une sémantique réitérable, contrairement à Stream.

Je pense que la raison principale est que Iterable implique la possibilité de le réutiliser, alors que Stream est quelque chose qui ne peut être utilisé qu'une seule fois - davantage comme un Iterator.

Si Stream extended Iterable, le code existant peut être surpris lorsqu'il reçoit une Iterable qui jette une Exception le deuxième fois ils font for (element : iterable).

176
kennytm

Pour convertir une Stream en une Iterable, vous pouvez faire

Stream<X> stream = null;
Iterable<X> iterable = stream::iterator

Pour passer une Stream à une méthode qui attend Iterable

void foo(Iterable<X> iterable)

simplement

foo(stream::iterator) 

mais cela a probablement l'air drôle; il pourrait être préférable d'être un peu plus explicite

foo( (Iterable<X>)stream::iterator );
140
ZhongYu

Je tiens à souligner que StreamEx implémente Iterable (et Stream), ainsi qu'un ensemble d'autres fonctionnalités extrêmement géniales qui manquent dans Stream.

10

kennytm a décrit pourquoi il est dangereux de traiter un Stream comme un Iterable, et Zhong Yu a proposé une solution de contournement qui permet d’utiliser un Stream comme dans Iterable, bien que de manière peu sûre. Il est possible d'obtenir le meilleur des deux mondes: une Iterable réutilisable à partir d'une Stream qui respecte toutes les garanties données par la spécification Iterable.

Remarque: SomeType n'est pas un paramètre de type ici. Vous devez le remplacer par un type approprié (par exemple, String) ou recourir à la réflexion.

Stream<SomeType> stream = ...;
Iterable<SomeType> iterable = stream.collect(toList()):

Il y a un inconvénient majeur:

Les avantages de l'itération paresseuse seront perdus. Si vous envisagez d'itérer immédiatement toutes les valeurs du thread actuel, le temps système sera négligeable. Toutefois, si vous envisagiez de n'itérer que partiellement ou dans un autre thread, cette itération immédiate et complète pourrait avoir des conséquences inattendues.

Le gros avantage, bien sûr, est que vous pouvez réutiliser la Iterable, alors que (Iterable<SomeType>) stream::iterator ne permettrait qu'un seul usage. Si le code de réception parcourt la collection plusieurs fois, cela est non seulement nécessaire, mais également bénéfique pour les performances.

6
Zenexer

Vous pouvez utiliser un flux dans une boucle for comme suit:

Stream<T> stream = ...;

for (T x : (Iterable<T>) stream::iterator) {
    ...
}

(Run cet extrait ici )

(Cette opération utilise une conversion d'interface fonctionnelle Java 8.)

(Ceci est couvert dans certains des commentaires ci-dessus (par exemple Aleksandr Dubinsky ), mais je voulais le tirer en réponse pour le rendre plus visible.)

3
Rich

Stream n'implémente pas Iterable. La compréhension générale de Iterable est tout ce qui peut être itéré, souvent encore et encore. Stream n'est peut-être pas rejouable.

La seule solution de contournement à laquelle je puisse penser, lorsqu'un itératif basé sur un flux est également lisible, consiste à recréer le flux. J'utilise un Supplier ci-dessous pour créer une nouvelle instance de flux, à chaque fois qu'un nouvel itérateur est créé. 

    Supplier<Stream<Integer>> streamSupplier = () -> Stream.of(10);
    Iterable<Integer> iterable = () -> streamSupplier.get().iterator();
    for(int i : iterable) {
        System.out.println(i);
    }
    // Can iterate again
    for(int i : iterable) {
        System.out.println(i);
    }
2
Ashish Tyagi

Si cela ne vous dérange pas d'utiliser des bibliothèques tierces cyclops-react définit un flux qui implémente à la fois Stream et Iterable et qui peut être rejoué (solution du problème kennytm décrit ).

 Stream<String> stream = ReactiveSeq.of("hello","world")
                                    .map(s->"prefix-"+s);

ou :-

 Iterable<String> stream = ReactiveSeq.of("hello","world")
                                      .map(s->"prefix-"+s);

 stream.forEach(System.out::println);
 stream.forEach(System.out::println);

[Divulgation Je suis le développeur principal de cyclops-react]

2
John McClean

Pas parfait, mais fonctionnera:

iterable = stream.collect(Collectors.toList());

Pas parfait parce qu'il va chercher tous les éléments du flux et les mettre dans cette List, ce qui n'est pas exactement ce que sont Iterable et Stream Ils sont supposés être paresseux .

0
yegor256

Vous pouvez parcourir tous les fichiers d'un dossier en utilisant Stream<Path> comme ceci:

Path path = Paths.get("...");
Stream<Path> files = Files.list(path);

for (Iterator<Path> it = files.iterator(); it.hasNext(); )
{
    Object file = it.next();

    // ...
}
0
BullyWiiPlaza