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

262

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

Iterator<T> iterator()

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

Quand je veux parcourir un Stream en utilisant 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?

gronder
la source
7
sans compter que les 2 autres méthodes d'itérables (forEach et spliterator) sont également en Stream
njzk2
1
cela est nécessaire pour passer Streamaux API héritées qui Iterable
attendent
12
Un bon IDE (par exemple IntelliJ) vous getIterable()return s::iterator;
invitera
24
Vous n'avez pas du tout besoin d'une méthode. Lorsque vous avez un Stream et que vous voulez un Iterable, passez simplement stream :: iterator (ou, si vous préférez, () -> stream.iterator ()), et vous avez terminé.
Brian Goetz
7
Malheureusement, je ne peux pas écrire for (T element : stream::iterator), donc je préférerais toujours que Stream implémente également Iterableune méthode toIterable().
Thorsten

Réponses:

197

Les gens ont déjà demandé la même chose sur la liste de diffusion ☺. La principale raison est qu'Iterable a également une sémantique réitérable, contrairement à Stream.

Je pense que la raison principale est que cela Iterableimplique la réutilisabilité, alors que Streamc'est quelque chose qui ne peut être utilisé qu'une seule fois - plus comme un Iterator.

S'il est Streamétendu, le Iterablecode existant pourrait être surpris lorsqu'il reçoit un Iterablequi lance une Exceptiondeuxième fois for (element : iterable).

kennytm
la source
22
Curieusement, il y avait déjà quelques itérables avec ce comportement dans Java 7, par exemple DirectoryStream: alors que DirectoryStream étend Iterable, ce n'est pas un Iterable à usage général car il ne prend en charge qu'un seul Iterator; l'appel de la méthode itérateur pour obtenir un second itérateur ou un itérateur suivant lève IllegalStateException. ( openjdk.java.net/projects/nio/javadoc/java/nio/file/… )
roim
32
Malheureusement, il n'y a rien dans la documentation Iterablesur la question de savoir si elle iteratordevrait toujours ou non être appelable plusieurs fois. C'est quelque chose qu'ils devraient y mettre. Cela semble être plus une pratique standard qu'une spécification formelle.
Lii
7
S'ils veulent utiliser des excuses, vous penseriez qu'ils pourraient au moins ajouter une méthode asIterable () - ou des surcharges pour toutes ces méthodes qui ne prennent que Iterable.
Trejkaz
26
Peut-être que la meilleure solution aurait été de faire en sorte que foreach de Java accepte un <T> Iterable ainsi que potentiellement un Stream <T>?
dévoré elysium
3
@Lii Cependant, selon les pratiques standard, elle est assez solide.
biziclop
161

Pour convertir un Streamen un Iterable, vous pouvez faire

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

Pour passer un 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 vaudrait peut-être mieux être un peu plus explicite

foo( (Iterable<X>)stream::iterator );
ZhongYu
la source
67
Vous pouvez également l'utiliser dans une boucle for(X x : (Iterable<X>)stream::iterator), même si cela semble laid. Vraiment, toute la situation est tout simplement absurde.
Aleksandr Dubinsky
24
@HRJIntStream.range(0,N).forEach(System.out::println)
MikeFHay
11
Je ne comprends pas la syntaxe des deux points dans ce contexte. Quelle est la différence entre stream::iteratoret stream.iterator(), qui rend le premier acceptable pour un, Iterablemais pas le second?
Daniel C.Sobral
20
Me répondre: Iterableest une interface fonctionnelle, donc passer une fonction qui l'implémente est suffisant.
Daniel C.Sobral
5
Je dois être d'accord avec @AleksandrDubinsky, le vrai problème est qu'il existe une solution de contournement symétrique pour (X x: (Iterable <X>) stream :: iterator), qui permet simplement la même opération, mais avec un bruit de code excellent. Mais alors c'est Java et nous avons toléré la liste <String> list = new ArrayList (Arrays.asList (array)) pendant longtemps :) Bien que les pouvoirs en place pourraient simplement nous offrir tous List <String> list = array. toArrayList (). Mais la vraie absurdité, c'est qu'en encourageant tout le monde à utiliser forEach on Stream, les exceptions doivent nécessairement être décochées .
Le Coordinateur
10

Je tiens à souligner que StreamExcela met en œuvre Iterable(et Stream), ainsi qu'une foule d'autres fonctionnalités extrêmement impressionnantes manquantes Stream.

Aleksandr Dubinsky
la source
8

Vous pouvez utiliser un Stream dans une forboucle comme suit:

Stream<T> stream = ...;

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

(Exécutez cet extrait ici )

(Cela utilise une distribution d'interface fonctionnelle Java 8.)

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

Riches
la source
Presque tout le monde doit l'examiner deux ou trois fois avant de se rendre compte de la façon dont il se compile. C'est absurde. (Ceci est couvert dans la réponse aux commentaires dans le même flux de commentaires, mais je voulais ajouter le commentaire ici pour le rendre plus visible.)
ShioT
7

kennytm a expliqué pourquoi il n'est pas sûr de traiter un Streamcomme un Iterable, et Zhong Yu a proposé une solution de contournement qui permet d'utiliser un Streamcomme dans Iterable, bien que de manière dangereuse. Il est possible d'obtenir le meilleur des deux mondes: un réutilisable à Iterablepartir d'un Streamqui répond à toutes les garanties apportées par le Iterablecahier des charges.

Remarque: SomeTypen'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, les frais généraux seront négligeables. Cependant, si vous prévoyez d'itérer seulement partiellement ou dans un thread différent, cette itération immédiate et complète pourrait avoir des conséquences inattendues.

Le grand avantage, bien sûr, est que vous pouvez réutiliser le Iterable, alors (Iterable<SomeType>) stream::iteratorqu'il ne permettrait qu'une seule utilisation. Si le code de réception itère plusieurs fois sur la collection, cela est non seulement nécessaire, mais probablement bénéfique pour les performances.

Zenexer
la source
1
Avez-vous essayé de compiler votre code avant de répondre? Ça ne marche pas.
Tagir Valeev
1
@TagirValeev Oui, je l'ai fait. Vous devez remplacer T par le type approprié. J'ai copié l'exemple du code de travail.
Zenexer
2
@TagirValeev Je l'ai testé à nouveau dans IntelliJ. Il semble qu'IntelliJ soit parfois dérouté par cette syntaxe; Je n'ai pas vraiment trouvé de modèle. Toutefois, le code compile correctement et IntelliJ supprime l'avis d'erreur après la compilation. Je suppose que c'est juste un bug.
Zenexer
1
Stream.toArray()renvoie un tableau, pas un Iterable, donc ce code ne compile toujours pas. mais cela peut être un bogue dans l'éclipse, car IntelliJ semble le compiler
benez
1
@Zenexer Comment avez-vous pu affecter un tableau à un Iterable?
radiantRazor
3

Streamne met pas en œuvre Iterable. La compréhension générale de Iterabletout ce qui peut être répété, souvent encore et encore. Streampeut ne pas être rejouable.

La seule solution de contournement à laquelle je peux penser, où un itérable basé sur un flux est également rejouable, est de recréer le flux. J'utilise un Supplierci - 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);
    }
Ashish Tyagi
la source
2

Si cela ne vous dérange pas d'utiliser des bibliothèques tierces, cyclops-react définit un Stream qui implémente à la fois Stream et Iterable et est également rejouable (résolution du problème décrit par Kennedy ).

 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]

John McClean
la source
0

Pas parfait, mais fonctionnera:

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

Pas parfait car il récupérera tous les éléments du flux et les placera dans ce Listqui n'est pas exactement de quoi Iterableil Streams'agit. Ils sont censés être paresseux .

yegor256
la source
-1

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();

    // ...
}
BullyWiiPlaza
la source