Comment convertir un itérateur en flux?

468

Je cherche un moyen concis de convertir un Iteratoren Streamou plus spécifiquement de "visualiser" l'itérateur comme un flux.

Pour des raisons de performances, je voudrais éviter une copie de l'itérateur dans une nouvelle liste:

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

Sur la base des quelques suggestions dans les commentaires, j'ai également essayé d'utiliser 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);
}

Cependant, je reçois un NoSuchElementException(car il n'y a pas d'invocation de 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)

J'ai regardé StreamSupportet Collectionsje n'ai rien trouvé.

gontard
la source
3
@DmitryGinzburg euh je ne veux pas créer un Stream "Infini".
gontard
1
@DmitryGinzburg Stream.generate(iterator::next)fonctionne?
gontard
1
@DmitryGinzburg Cela ne fonctionnera pas pour un itérateur fini.
assylias

Réponses:

543

Une façon consiste à créer un séparateur à partir de l'itérateur et à l'utiliser comme base pour votre flux:

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

Une alternative qui est peut-être plus lisible est d'utiliser un Iterable - et créer un Iterable à partir d'un Iterator est très facile avec lambdas car Iterable est une interface fonctionnelle:

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

Iterable<String> iterable = () -> sourceIterator;
Stream<String> targetStream = StreamSupport.stream(iterable.spliterator(), false);
assylias
la source
26
Les flux sont paresseux: le code relie uniquement le flux à l'itérateur, mais l'itération réelle n'aura pas lieu avant une opération de terminal. Si vous utilisez l'itérateur entre-temps, vous n'obtiendrez pas le résultat attendu. Par exemple, vous pouvez introduire un sourceIterator.next()avant d'utiliser le flux et vous verrez l'effet (le premier élément ne sera pas vu par le flux).
assylias
9
@assylias, ouais c'est vraiment sympa! Peut-être pourriez-vous expliquer aux futurs lecteurs cette ligne assez magique Iterable<String> iterable = () -> sourceIterator;. Je dois admettre qu'il m'a fallu un certain temps pour comprendre.
gontard
7
Je devrais dire ce que j'ai trouvé. Iterable<T>est un FunctionalInterfacequi n'a qu'une seule méthode abstraite iterator(). Il en () -> sourceIteratorva de même pour une expression lambda instanciant une Iterableinstance en tant qu'implémentation anonyme.
Jin Kwon
13
Encore () -> sourceIterator;une fois, est une forme abrégée denew Iterable<>() { @Override public Iterator<String> iterator() { return sourceIterator; } }
Jin Kwon
7
@JinKwon Ce n'est pas vraiment une forme abrégée d'une classe anonyme (il y a quelques différences subtiles, telles que la portée et la façon dont elle est compilée) mais elle se comporte de la même manière dans ce cas.
assylias
122

Depuis la version 21, la bibliothèque Guava fournit Streams.stream(iterator)

Il fait ce que @ assylias l » montre de réponse .

numéro6
la source
Il est préférable de l'utiliser de manière cohérente jusqu'à ce que le JDK prenne en charge un one-liner natif. Il sera beaucoup plus simple de trouver (et donc de refactoriser) à l'avenir que les solutions purement JDK présentées ailleurs.
drekbour
C'est excellent mais… comment Java a-t-il des itérateurs et des flux natifs… mais pas de manière simple et directe de passer de l'un à l'autre!? Une omission à mon avis.
Dan Lenski
92

Grande suggestion! Voici ma version réutilisable:

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

Et l'utilisation (assurez-vous d'importer statiquement asStream):

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

C'est possible dans Java 9.

Stream.generate(() -> null)
    .takeWhile(x -> iterator.hasNext())
    .map(n -> iterator.next())
    .forEach(System.out::println);
PhilipRoman
la source
1
Simple, efficace et sans recourir au sous-classement - telle devrait être la réponse acceptée!
martyglaubitz
1
Malheureusement, ceux-ci ne semblent pas fonctionner avec les .parallel()flux. Ils semblent également un peu plus lents que d'aller plus loin Spliterator, même pour une utilisation séquentielle.
Thomas Ahle
De plus, la première méthode apparaît si l'itérateur est vide. La deuxième méthode fonctionne pour l'instant, mais elle viole l'exigence des fonctions dans map et takeWhile d'être sans état, donc j'hésiterais à le faire dans le code de production.
Hans-Peter Störr
Vraiment, cela devrait être une réponse acceptée. Même si cela parallelpeut être génial, la simplicité est incroyable.
Sven
11

Créer Spliteratorà Iteratorpartir de la Spliteratorsclasse contient plus d'une fonction pour créer un séparateur, par exemple ici, j'utilise spliteratorUnknownSizequi obtient l'itérateur comme paramètre, puis créer un flux à l'aideStreamSupport

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

et utiliser Streams.stream(iterator):

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

Utilisation Collections.list(iterator).stream()...

Israel CS Rocha
la source
6
Bien que court, c'est très peu performant.
Olivier Grégoire
2
Cela dépliera tout l'itérateur en objet java, puis le convertira en flux. Je ne le suggère pas
iec2011007
3
Cela semble être uniquement pour les énumérations, pas les itérateurs.
john16384
1
Pas une réponse terrible en général, utile dans un pincement, mais la question mentionne la performance et la réponse n'est pas performante.
Traîneau