Wie konvertiere ich einen Iterator in einen Stream?

468

Ich suche nach einer prägnanten Möglichkeit, einen Iteratorin einen Streamoder genauer zu konvertieren, um den Iterator als Stream "anzuzeigen".

Aus Leistungsgründen möchte ich eine Kopie des Iterators in einer neuen Liste vermeiden:

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

Aufgrund einiger Vorschläge in den Kommentaren habe ich auch versucht zu verwenden 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);
}

Ich bekomme jedoch eine NoSuchElementException(da es keinen Aufruf von gibt 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)

Ich habe angeschaut StreamSupportund Collectionsaber nichts gefunden.

Gontard
quelle
3
@DmitryGinzburg euh ich möchte keinen "unendlichen" Stream erstellen.
Pontard
1
@DmitryGinzburg Stream.generate(iterator::next)funktioniert?
Pontard
1
@DmitryGinzburg Das funktioniert nicht für einen endlichen Iterator.
Assylias

Antworten:

543

Eine Möglichkeit besteht darin, einen Spliterator aus dem Iterator zu erstellen und diesen als Grundlage für Ihren Stream zu verwenden:

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

Eine Alternative, die vielleicht besser lesbar ist, ist die Verwendung eines Iterable - und das Erstellen eines Iterables aus einem Iterator ist mit Lambdas sehr einfach, da Iterable eine funktionale Schnittstelle ist:

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

Iterable<String> iterable = () -> sourceIterator;
Stream<String> targetStream = StreamSupport.stream(iterable.spliterator(), false);
Assylien
quelle
26
Streams sind faul: Der Code verbindet den Stream nur mit dem Iterator, aber die eigentliche Iteration erfolgt erst nach einer Terminaloperation. Wenn Sie den Iterator in der Zwischenzeit verwenden, erhalten Sie nicht das erwartete Ergebnis. Zum Beispiel können Sie ein einführen, sourceIterator.next()bevor Sie den Stream verwenden, und Sie werden den Effekt sehen (das erste Element wird vom Stream nicht gesehen).
Assylias
9
@assylias, ja es ist wirklich schön! Vielleicht könnten Sie zukünftigen Lesern diese ziemlich magische Linie erklären Iterable<String> iterable = () -> sourceIterator;. Ich muss zugeben, dass ich einige Zeit gebraucht habe, um zu verstehen.
Pontard
7
Ich sollte sagen, was ich gefunden habe. Iterable<T>ist eine, FunctionalInterfacedie nur eine abstrakte Methode hat iterator(). Dies () -> sourceIteratorgilt auch für einen Lambda-Ausdruck, der eine IterableInstanz als anonyme Implementierung instanziiert .
Jin Kwon
13
Wieder () -> sourceIterator;ist eine verkürzte Form vonnew Iterable<>() { @Override public Iterator<String> iterator() { return sourceIterator; } }
Jin Kwon
7
@JinKwon Es handelt sich nicht wirklich um eine verkürzte Form einer anonymen Klasse (es gibt einige subtile Unterschiede, wie z. B. Umfang und Kompilierung), aber in diesem Fall verhält es sich ähnlich.
Assylias
122

Seit Version 21 bietet die Guava-Bibliothek Streams.stream(iterator)

Es tut , was @ assylias ‚s Antwort zeigt .

numéro6
quelle
Es ist weitaus besser, dies konsequent zu verwenden, bis das JDK einen nativen Einzeiler unterstützt. Es wird in Zukunft viel einfacher sein, dies zu finden (daher zu überarbeiten) als die an anderer Stelle gezeigten reinen JDK-Lösungen.
Drekbour
Das ist ausgezeichnet, aber ... wie hat Java native Iteratoren und Streams ... aber keinen eingebauten, einfachen Weg, um von einem zum anderen zu gelangen!? Eine Auslassung meiner Meinung nach.
Dan Lenski
92

Toller Vorschlag! Hier ist meine wiederverwendbare Sichtweise:

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

Und Verwendung (stellen Sie sicher, dass asStream statisch importiert wird):

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

Dies ist in Java 9 möglich.

Stream.generate(() -> null)
    .takeWhile(x -> iterator.hasNext())
    .map(n -> iterator.next())
    .forEach(System.out::println);
PhilipRoman
quelle
1
Einfach, effizient und ohne Unterklassen - das sollte die akzeptierte Antwort sein!
Martyglaubitz
1
Leider scheinen diese nicht mit .parallel()Streams zu funktionieren . Sie erscheinen auch etwas langsamer als das Übergehen Spliterator, selbst für die sequentielle Verwendung.
Thomas Ahle
Außerdem wird die erste Methode ausgelöst, wenn der Iterator leer ist. Die zweite Methode funktioniert vorerst, verstößt jedoch gegen die Anforderungen der Funktionen in map und takeWhile, um zustandslos zu sein. Daher würde ich zögern, dies im Produktionscode zu tun.
Hans-Peter Störr
Dies sollte wirklich eine akzeptierte Antwort sein. Auch wenn paralleles funky sein mag, ist die Einfachheit erstaunlich.
Sven
11

Create Spliteratorfrom Iteratorusing Spliteratorsclass enthält mehr als eine Funktion zum Erstellen eines Spliterators. Hier wird beispielsweise verwendet spliteratorUnknownSize, um den Iterator als Parameter abzurufen und anschließend Stream mithilfe von zu erstellenStreamSupport

Spliterator<Model> spliterator = Spliterators.spliteratorUnknownSize(
        iterator, Spliterator.NONNULL);
Stream<Model> stream = StreamSupport.stream(spliterator, false);
Bassem Reda Zohdy
quelle
1
import com.google.common.collect.Streams;

und verwenden Streams.stream(iterator):

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

Verwenden Collections.list(iterator).stream()...

Israel CS Rocha
quelle
6
Dies ist zwar kurz, aber sehr unterdurchschnittlich.
Olivier Grégoire
2
Dadurch wird der gesamte Iterator in ein Java-Objekt umgewandelt und anschließend in einen Stream konvertiert. Ich schlage es nicht vor
iec2011007
3
Dies scheint nur für Aufzählungen zu sein, nicht für Iteratoren.
John 16384
1
Keine schreckliche Antwort im Allgemeinen, zur Not nützlich, aber die Frage erwähnt die Leistung und die Antwort ist nicht performant.
Schlitten