web-dev-qa-db-fra.com

Comment transformer une énumération Java en un flux?

J'ai une bibliothèque tierce qui me donne un Enumeration<String>. Je veux travailler avec cette énumération paresseusement en tant que Java 8 Stream, en appelant des choses comme filter, map et flatMap dessus.

Y a-t-il une bibliothèque existante qui contient cela? Je fais déjà référence à Guava et Apache Commons, donc si l'un ou l'autre a la solution qui serait idéale.

Sinon, quelle est la meilleure façon/la plus simple de transformer un Enumeration en Stream tout en conservant la nature paresseuse de tout?

52
Micah Zoltu

Cette réponse fournit déjà une solution qui crée un Stream à partir d'un Enumeration:

 public static <T> Stream<T> enumerationAsStream(Enumeration<T> e) {
     return StreamSupport.stream(
         Spliterators.spliteratorUnknownSize(
             new Iterator<T>() {
                 public T next() {
                     return e.nextElement();
                 }
                 public boolean hasNext() {
                     return e.hasMoreElements();
                 }
             },
             Spliterator.ORDERED), false);
 }

Il convient de souligner que le Stream résultant est aussi paresseux que tout autre Stream, car il ne sera pas traité tous les éléments avant le début de l'action du terminal et si le fonctionnement du terminal est en court-circuit, il itérera uniquement autant d'éléments que nécessaire.

Pourtant, elle peut encore être améliorée. J'ajouterais toujours une méthode forEachRemaining lorsqu'il existe un moyen simple de traiter tous les éléments. Cette méthode sera appelée par l'implémentation Stream pour la plupart des opérations sans court-circuit:

public static <T> Stream<T> enumerationAsStream(Enumeration<T> e) {
    return StreamSupport.stream(
        Spliterators.spliteratorUnknownSize(
            new Iterator<T>() {
                public T next() {
                    return e.nextElement();
                }
                public boolean hasNext() {
                    return e.hasMoreElements();
                }
                public void forEachRemaining(Consumer<? super T> action) {
                    while(e.hasMoreElements()) action.accept(e.nextElement());
                }
            },
            Spliterator.ORDERED), false);
}

Cependant, le code ci-dessus est victime de l'anti-modèle "utilisant Iterator parce qu'il est si familier". Le Iterator créé sera enveloppé dans une implémentation de la nouvelle interface Spliterator et ne fournit aucun avantage par rapport à l'implémentation de Spliterator directement:

public static <T> Stream<T> enumerationAsStream(Enumeration<T> e) {
    return StreamSupport.stream(
        new Spliterators.AbstractSpliterator<T>(Long.MAX_VALUE, Spliterator.ORDERED) {
            public boolean tryAdvance(Consumer<? super T> action) {
                if(e.hasMoreElements()) {
                    action.accept(e.nextElement());
                    return true;
                }
                return false;
            }
            public void forEachRemaining(Consumer<? super T> action) {
                while(e.hasMoreElements()) action.accept(e.nextElement());
            }
    }, false);
}

Au niveau du code source, cette implémentation est aussi simple que celle basée sur Iterator, mais élimine la délégation d'un Spliterator à un Iterator. Il suffit à ses lecteurs de se renseigner sur la nouvelle API.

41
Holger

Pourquoi ne pas utiliser Vanilla Java:

Collections.list(enumeration).stream()...

Cependant, comme mentionné par @MicahZoltu, le nombre d'éléments dans l'énumération doit être pris en compte, comme Collections.list va d'abord parcourir l'énumération pour copier les éléments dans un ArrayList. De là, la méthode régulière stream peut être utilisée. Bien que cela soit habituel pour de nombreuses opérations de flux de collection, si l'énumération est trop grande (comme infinie), cela peut poser problème car l'énumération doit être transformée en liste, puis les autres approches décrites ici doivent être utilisées à la place.

87
Brice

Dans Java 9, il est possible de convertir un Enumeration en Stream avec une ligne:

Enumeration<String> en = ... ;
Stream<String> str = StreamSupport.stream(
    Spliterators.spliteratorUnknownSize(en.asIterator(), Spliterator.ORDERED),
    false
);

(Eh bien, c'est une longue file.)

Si vous n'êtes pas sur Java 9, vous pouvez convertir le Enumeration en Iterator manuellement en utilisant la technique indiquée dans Holger's answer =.

24
Stuart Marks

Selon Guava docs , vous pouvez utiliser la méthode Iterators.forEnumeration():

Enumeration<Something> enumeration = ...;

Iterator<SomeThing> iterator = Iterators.forEnumeration(enumeration);

Et dans cette question , il est expliqué comment obtenir un flux depuis un itérateur:

Stream<Something> stream = StreamSupport.stream(
    Spliterators.spliteratorUnknownSize(
        iterator, Spliterator.ORDERED),
    false);

Dans ma bibliothèque StreamEx il y a une méthode simple StreamEx.of(Enumeration) qui fait le travail:

Stream<String> stream = StreamEx.of(enumeration);

Notez qu'il ne s'agit pas seulement d'un raccourci vers la solution @Holger, mais implémenté de manière différente. En particulier, il présente des caractéristiques d'exécution parallèle nettement meilleures par rapport aux solutions impliquant Spliterators.spliteratorUnknownSize().

4
Tagir Valeev