Come convertire un iteratore in un flusso?


468

Sto cercando un modo conciso per convertire un Iteratorin Streamo più specificamente per "visualizzare" l'iteratore come stream.

Per motivi di prestazioni, vorrei evitare una copia dell'iteratore in un nuovo elenco:

Iterator<String> sourceIterator = Arrays.asList("A", "B", "C").iterator();
Collection<String> copyList = new ArrayList<String>();
sourceIterator.forEachRemaining(copyList::add);
Stream<String> targetStream = copyList.stream();

Sulla base di alcuni suggerimenti nei commenti, ho anche cercato di utilizzare Stream.generate:

public static void main(String[] args) throws Exception {
    Iterator<String> sourceIterator = Arrays.asList("A", "B", "C").iterator();
    Stream<String> targetStream = Stream.generate(sourceIterator::next);
    targetStream.forEach(System.out::println);
}

Tuttavia, ottengo un NoSuchElementException(poiché non vi è alcuna chiamata di hasNext)

Exception in thread "main" java.util.NoSuchElementException
    at java.util.AbstractList$Itr.next(AbstractList.java:364)
    at Main$$Lambda$1/1175962212.get(Unknown Source)
    at java.util.stream.StreamSpliterators$InfiniteSupplyingSpliterator$OfRef.tryAdvance(StreamSpliterators.java:1351)
    at java.util.Spliterator.forEachRemaining(Spliterator.java:326)
    at java.util.stream.ReferencePipeline$Head.forEach(ReferencePipeline.java:580)
    at Main.main(Main.java:20)

Ho guardato StreamSupporte Collectionsnon ho trovato nulla.



3
@DmitryGinzburg euh non voglio creare un flusso "Infinito".
Gontard,

1
@DmitryGinzburg Stream.generate(iterator::next)funziona?
Gontard,

1
@DmitryGinzburg Questo non funzionerà per un iteratore finito.
assylias,

Risposte:


543

Un modo è quello di creare uno Spliterator da Iterator e utilizzarlo come base per il tuo stream:

Iterator<String> sourceIterator = Arrays.asList("A", "B", "C").iterator();
Stream<String> targetStream = StreamSupport.stream(
          Spliterators.spliteratorUnknownSize(sourceIterator, Spliterator.ORDERED),
          false);

Un'alternativa forse più leggibile è usare un Iterable - e creare un Iterable da un Iterator è molto facile con lambdas perché Iterable è un'interfaccia funzionale:

Iterator<String> sourceIterator = Arrays.asList("A", "B", "C").iterator();

Iterable<String> iterable = () -> sourceIterator;
Stream<String> targetStream = StreamSupport.stream(iterable.spliterator(), false);

26
Gli stream sono pigri: il codice collega solo lo stream all'Iteratore ma l'iterazione effettiva non avverrà fino a un'operazione terminale. Se usi l'iteratore nel frattempo non otterrai il risultato atteso. Ad esempio, puoi introdurre un sourceIterator.next()prima di utilizzare lo stream e vedrai l'effetto (il primo elemento non verrà visualizzato dallo Stream).
assylias,

9
@assylias, sì, è davvero bello! Forse potresti spiegare ai futuri lettori questa linea piuttosto magica Iterable<String> iterable = () -> sourceIterator;. Devo ammettere che mi ci è voluto del tempo per capire.
Gontard,

7
Dovrei dire cosa ho trovato. Iterable<T>è un FunctionalInterfacemetodo che ha un solo metodo astratto iterator(). Quindi () -> sourceIteratorun'espressione lambda crea Iterableun'istanza come istanza anonima.
Jin Kwon,

13
Ancora () -> sourceIterator;una volta, è una forma abbreviata dinew Iterable<>() { @Override public Iterator<String> iterator() { return sourceIterator; } }
Jin Kwon

7
@JinKwon In realtà non è una forma abbreviata di una classe anonima (ci sono alcune sottili differenze, come l'ambito e il modo in cui è compilata), ma in questo caso si comporta in modo simile.
Assylias,

122

Dalla versione 21, fornisce la libreria Guava Streams.stream(iterator)

Si fa quello @ assylias 's risposta spettacoli .



Molto meglio usarlo in modo coerente fino a quando JDK non supporta un one-liner nativo. Sarà molto più semplice trovare (quindi il refactor) in futuro rispetto alle soluzioni pure-JDK mostrate altrove.
drekbour

Questo è eccellente ma ... in che modo Java ha iteratori e stream nativi ... ma nessun modo integrato e semplice per passare dall'uno all'altro !? Un'omissione secondo me.
Dan Lenski,

92

Ottimo consiglio! Ecco la mia versione riutilizzabile:

public class StreamUtils {

    public static <T> Stream<T> asStream(Iterator<T> sourceIterator) {
        return asStream(sourceIterator, false);
    }

    public static <T> Stream<T> asStream(Iterator<T> sourceIterator, boolean parallel) {
        Iterable<T> iterable = () -> sourceIterator;
        return StreamSupport.stream(iterable.spliterator(), parallel);
    }
}

E utilizzo (assicurati di importare staticamente come stream):

List<String> aPrefixedStrings = asStream(sourceIterator)
                .filter(t -> t.startsWith("A"))
                .collect(toList());

43

Questo è possibile in Java 9.

Stream.generate(() -> null)
    .takeWhile(x -> iterator.hasNext())
    .map(n -> iterator.next())
    .forEach(System.out::println);

1
Semplice, efficiente e senza ricorrere alla sottoclasse: questa dovrebbe essere la risposta accettata!
martyglaubitz,

1
Sfortunatamente questi non sembrano funzionare con i .parallel()flussi. Sembrano anche un po 'più lenti che andare oltre Spliterator, anche per un uso sequenziale.
Thomas Ahle,

Inoltre, il primo metodo viene generato se l'iteratore è vuoto. Il secondo metodo funziona per ora, ma viola il requisito delle funzioni in map e takeWhile è senza stato, quindi esiterei a farlo nel codice di produzione.
Hans-Peter Störr

Davvero, questa dovrebbe essere una risposta accettata. Anche se parallelpotrebbe essere funky, la semplicità è sorprendente.
Sabato

11

Crea Spliteratordalla classe di Iteratorutilizzo Spliteratorscontiene più di una funzione per la creazione di spliterator, ad esempio sto usando spliteratorUnknownSizequale sta ottenendo iterator come parametro, quindi crea Stream usandoStreamSupport

Spliterator<Model> spliterator = Spliterators.spliteratorUnknownSize(
        iterator, Spliterator.NONNULL);
Stream<Model> stream = StreamSupport.stream(spliterator, false);

1
import com.google.common.collect.Streams;

e usa Streams.stream(iterator):

Streams.stream(iterator)
       .map(v-> function(v))
       .collect(Collectors.toList());


-4

Uso Collections.list(iterator).stream()...


6
Mentre breve, questo è molto poco performante.
Olivier Grégoire,

2
Questo spiegherà l'intero iteratore in oggetto java, quindi lo convertirà in streaming. Non lo consiglio
iec2011007,

3
Questo sembra essere solo per le enumerazioni, non per gli iteratori.
john16384,

1
Non è una risposta terribile in generale, utile in un pizzico, ma la domanda menziona le prestazioni e la risposta non è performante.
Slitta
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.