Perché Stream <T> non implementa Iterable <T>?


262

In Java 8 abbiamo la classe Stream <T> , che curiosamente ha un metodo

Iterator<T> iterator()

Quindi ti aspetteresti che implementa l'interfaccia Iterable <T> , che richiede esattamente questo metodo, ma non è così.

Quando voglio iterare su uno Stream usando un ciclo foreach, devo fare qualcosa del genere

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)) { ... }

Mi sto perdendo qualcosa qui?


7
per non parlare del fatto che anche gli altri 2 metodi di iterabile (per ogni e spliteratore) sono in Stream
njzk2

1
questo è necessario per passare Streamalle API legacy che si aspettanoIterable
ZhongYu il

12
Un buon IDE (ad esempio IntelliJ) vi verrà chiesto di semplificare il codice in getIterable()areturn s::iterator;
Zhongyu

24
Non hai bisogno di un metodo. Dove hai uno Stream e vuoi un Iterable, passa semplicemente stream :: iterator (o, se preferisci, () -> stream.iterator ()), e il gioco è fatto.
Brian Goetz,

7
Sfortunatamente non posso scrivere for (T element : stream::iterator), quindi preferirei comunque che Stream implementasse Iterableo un metodo toIterable().
Thorsten,

Risposte:


197

Le persone hanno già chiesto lo stesso sulla mailing list ☺. Il motivo principale è Iterable ha anche una semantica reiterabile, mentre Stream no.

Penso che il motivo principale sia che Iterableimplica la riusabilità, mentre Streamè qualcosa che può essere usato solo una volta - più come un Iterator.

Se Streamesteso, il Iterablecodice esistente potrebbe essere sorpreso quando riceve un oggetto Iterableche viene lanciato una Exceptionseconda volta for (element : iterable).


22
Curiosamente c'erano già alcuni iterabili con questo comportamento in Java 7, ad esempio DirectoryStream: Mentre DirectoryStream estende Iterable, non è un Iterable per tutti gli usi in quanto supporta solo un singolo Iterator; invocare il metodo iteratore per ottenere un secondo o successivo iteratore genera IllegalStateException. ( openjdk.java.net/projects/nio/javadoc/java/nio/file/… )
percorso

32
Sfortunatamente non c'è nulla della documentazione Iterablesu se iteratordebba sempre o meno essere richiamabile più volte. È qualcosa che dovrebbero mettere lì. Questa sembra essere più una pratica standard che una specifica formale.
Lii,

7
Se useranno delle scuse, penseresti che potrebbero almeno aggiungere un metodo asIterable () o sovraccarichi per tutti quei metodi che accettano solo Iterable.
Trejkaz,

26
Forse la soluzione migliore sarebbe stata far sì che Java foreach accettasse un Iterable <T> e potenzialmente un Stream <T>?
divorò l'elisio il

3
@Lii Come vanno le pratiche standard, è comunque abbastanza forte.
biziclop,

161

Per convertire un Streamin un Iterable, puoi farlo

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

Per passare Streama un metodo che prevede Iterable,

void foo(Iterable<X> iterable)

semplicemente

foo(stream::iterator) 

tuttavia probabilmente sembra divertente; potrebbe essere meglio essere un po 'più esplicito

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

67
Puoi anche usarlo in un ciclo for(X x : (Iterable<X>)stream::iterator), anche se sembra brutto. Davvero, l'intera situazione è semplicemente assurda.
Aleksandr Dubinsky,

24
@HRJIntStream.range(0,N).forEach(System.out::println)
MikeFHay,

11
Non capisco la sintassi dei due punti in questo contesto. Qual è la differenza tra stream::iteratore stream.iterator(), che rende accettabile la prima per una Iterablema non la seconda?
Daniel C. Sobral,

20
Rispondere a me stesso: Iterableè un'interfaccia funzionale, quindi è sufficiente passare una funzione che la implementa.
Daniel C. Sobral,

5
Sono d'accordo con @AleksandrDubinsky, Il vero problema è che esiste una soluzione alternativa per (X x: (Iterable <X>) stream :: iteratore), che consente la stessa operazione anche se con un ottimo rumore del codice. Ma poi questo è Java e abbiamo tollerato la lista <String> list = new ArrayList (Arrays.asList (array)) per molto tempo :) Sebbene i poteri che potrebbero essere in grado di offrirci tutti List <String> list = array. toArrayList (). Ma la vera assurdità è che, incoraggiando tutti a utilizzare per ciascuno su Stream, le eccezioni devono essere necessariamente deselezionate [ non terminato].
Il coordinatore

10

Vorrei sottolineare che StreamEximplementa Iterable(e Stream), oltre a una serie di altre funzionalità immensamente fantastiche che mancano Stream.


8

Puoi usare uno Stream in un forciclo come segue:

Stream<T> stream = ...;

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

(Esegui questo frammento qui )

(Questo utilizza un cast di interfaccia funzionale Java 8).

(Questo è coperto in alcuni dei commenti sopra (ad esempio Aleksandr Dubinsky ), ma volevo estrarlo in una risposta per renderlo più visibile.)


Quasi tutti devono guardarlo due o tre volte prima di rendersi conto di come si compila. È assurdo. (Questo è coperto nella risposta ai commenti nello stesso flusso di commenti, ma volevo aggiungere il commento qui per renderlo più visibile.)
ShioT

7

kennytm ha descritto perché non è sicuro trattare un Streamcome an Iterablee Zhong Yu ha offerto una soluzione alternativa che consente di utilizzare un Streamas in Iterable, anche se in modo non sicuro. È possibile ottenere il meglio da entrambi i mondi: un riutilizzabile Iterableda uno Streamche soddisfa tutte le garanzie fornite dalle Iterablespecifiche.

Nota: SomeTypenon è un parametro di tipo qui - è necessario sostituirlo con un tipo corretto (ad es. String) O ricorrere alla riflessione

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

C'è un grosso svantaggio:

I benefici dell'iterazione lenta andranno persi. Se hai pianificato di iterare immediatamente su tutti i valori nel thread corrente, qualsiasi overhead sarà trascurabile. Tuttavia, se hai pianificato di iterare solo parzialmente o in un thread diverso, questa iterazione immediata e completa potrebbe avere conseguenze non intenzionali.

Il grande vantaggio, ovviamente, è che puoi riutilizzare il Iterable, mentre (Iterable<SomeType>) stream::iteratorconsentirebbe un solo uso. Se il codice di ricezione ripeterà più volte la raccolta, questo non è solo necessario, ma probabilmente vantaggioso per le prestazioni.


1
Hai provato a compilare il codice prima di rispondere? Non funziona
Tagir Valeev,

1
@TagirValeev Sì, l'ho fatto. È necessario sostituire T con il tipo appropriato. Ho copiato l'esempio dal codice di lavoro.
Zenexer,

2
@TagirValeev L'ho provato di nuovo in IntelliJ. Sembra che IntelliJ a volte venga confuso da questa sintassi; Non ho davvero trovato un modello. Tuttavia, il codice viene compilato correttamente e IntelliJ rimuove l'avviso di errore dopo la compilazione. Immagino sia solo un bug.
Zenexer,

1
Stream.toArray()restituisce un array, non un Iterable, quindi questo codice non viene ancora compilato. ma potrebbe essere un bug in eclissi, poiché IntelliJ sembra compilarlo
benez

1
@Zenexer Come hai potuto assegnare un array a un Iterable?
radiantRazor

3

Streamnon implementa Iterable. La comprensione generale di Iterabletutto ciò che può essere ripetuto, spesso ancora e ancora. Streampotrebbe non essere riproducibile.

L'unica soluzione che mi viene in mente, in cui è riproducibile anche un iterabile basato su uno stream, è ricreare lo stream. Sto usando un Suppliersotto per creare una nuova istanza di stream, ogni volta che viene creato un nuovo iteratore.

    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

Se non ti dispiace usare librerie di terze parti cyclops- reazioni definisce uno Stream che implementa sia Stream che Iterable ed è anche riproducibile (risolvendo il problema descritto da kennytm ).

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

o :-

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

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

[Divulgazione Sono lo sviluppatore principale di cyclops-reagire]


0

Non perfetto, ma funzionerà:

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

Non perfetto perché recupererà tutti gli elementi dallo stream e li inserirà in quello List, che non è esattamente cosa Iterablee di cosa Streamsi tratta. Dovrebbero essere pigri .


-1

Puoi iterare su tutti i file in una cartella usando in Stream<Path>questo modo:

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

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

    // ...
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.