Wie verwandle ich eine Java-Aufzählung in einen Stream?

73

Ich habe eine Drittanbieter-Bibliothek, die mir eine gibt Enumeration<String>. Ich möchte mit dieser Aufzählung lazily als Java - 8 arbeiten Stream, rufen Dinge wie filter, mapund flatMapauf sie.

Gibt es eine vorhandene Bibliothek, in der dies enthalten ist? Ich beziehe mich bereits auf Guava und Apache Commons. Wenn also einer von beiden die Lösung hat, wäre das ideal.

Alternativ, was ist der beste / einfachste Weg, um aus einer Zeit Enumerationeine StreamFaulheit zu machen?

Micah Zoltu
quelle
1
Verwandte: Iterieren Sie eine Aufzählung in Java 8
Nathan Hughes
1
Die verknüpfte Frage lautet, wie aus einem Enumeration(Java 1.0) ein Iterator(Java 1.2) wird. Ich frage, wie man daraus ein Stream(Java 1.8) macht. Während es so aussieht, als ob die letzte Antwort in der verknüpften Frage dies beantwortet, ist diese Antwort für die gestellte Frage falsch . Diese Antwort sollte hier angegeben werden, damit zukünftige Suchende sie erfolgreich finden können. Vielleicht möchte @ArneBurmeister die Antwort hier kopieren, damit diese Frage direkt beantwortet wird?
Micah Zoltu
3
Wiedereröffnet, da die Antworten auf die verknüpfte Frage das faule Verhalten nicht diskutieren und es auch nicht der richtige Ort wäre, alternative Möglichkeiten zum Erstellen einer zu veröffentlichen Stream(da dies nicht der Umfang der verknüpften Frage ist).
Holger

Antworten:

51

Diese Antwort bietet bereits eine Lösung, die Streamaus einem 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);
 }

Es sollte betont werden , dass die resultierende Stream ist wie jede andere als faul Stream, da es keine Produkte verarbeiten , bevor das Terminal Aktion begonnen wurde , und wenn die Terminalbetrieb Kurzschluss ist, wird es nur so viele Punkte wie nötig wiederholen.

Dennoch gibt es Raum für Verbesserungen. Ich würde immer eine forEachRemainingMethode hinzufügen , wenn es eine einfache Möglichkeit gibt, alle Elemente zu verarbeiten. Diese Methode wird von der StreamImplementierung für die meisten nicht kurzgeschlossenen Operationen aufgerufen :

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

Der obige Code ist jedoch ein Opfer des IteratorAntipatterns "Verwenden, weil es so vertraut ist". Das erstellte Iteratorwird in eine Implementierung der neuen SpliteratorSchnittstelle eingebunden und bietet keinen Vorteil gegenüber der Spliteratordirekten Implementierung :

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

Auf der Quellcode-Ebene ist diese Implementierung so einfach wie die Iterator-basierte, eliminiert jedoch die Delegierung von a Spliteratornach a Iterator. Die Leser müssen lediglich die neue API kennenlernen.

Holger
quelle
Gutes Zeug Holger. Was sind die Vorteile der Verwendung von Spliterator.ORDERED hier gegenüber anderen Werten?
IcedDante
8
@IcedDante ORDEREDbedeutet , dass es eine definierte Begegnung Ordnung, so bedeutet dies , dass die Stream - Implementierung ist nicht Optimierungen zu tun erlaubt , basierend auf die Daten unter der Annahme , ungeordnete zu sein. Da Enumerationwir für ein Unbekanntes nicht wissen, ob die Reihenfolge eine Bedeutung hat, müssen wir davon ausgehen, dass sie eine Bedeutung haben könnte , und dieses Merkmal spezifizieren. Der Anrufer kann unordered()den Stream weiterhin aufrufen, wenn er weiß, dass die Reihenfolge für die jeweiligen Daten irrelevant ist, um Optimierungen zu ermöglichen. Unsere anfängliche Prämisse muss jedoch sein, dass die Reihenfolge wichtig sein könnte.
Holger
Beachten Sie, dass es Enumeration#asIterator()seit Java 9.
Dan1st
139

Warum nicht Vanille Java verwenden:

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

Wie von @MicahZoltu erwähnt, muss jedoch die Anzahl der Elemente in der Aufzählung berücksichtigt werden, da Collections.listzuerst die Aufzählung durchlaufen wird, um die Elemente in eine zu kopieren ArrayList. Von dort aus kann die reguläre streamMethode angewendet werden. Während dies für viele Sammlungsstromoperationen üblich ist, kann dies zu Problemen führen, wenn die Aufzählung zu groß ist (wie unendlich), da die Aufzählung in eine Liste transformiert werden muss. Stattdessen sollten stattdessen die anderen hier beschriebenen Ansätze verwendet werden.

Brice
quelle
12
Dadurch wird das gesamte System aufgelistet enumeration, um daraus eine Liste zu erstellen, und Sie erhalten dann Stream-Zugriff auf die Liste. Wenn die Aufzählung klein ist, ist dies wahrscheinlich ein vernünftiger Ansatz. Wenn die Aufzählung sehr groß ist, kann dies eine teure und unnötige Operation sein. Wenn die Aufzählung unendlich ist, stürzt Ihre Anwendung ab.
Micah Zoltu
1
@ MicahZoltu In der Tat. Das ist ein zu berücksichtigender Punkt, ich werde die Antwort aktualisieren. Danke
Brice
Ich mag deine Lösung!
Dehasi
38

In Java 9 ist es möglich, ein mit einem Einzeiler Enumerationin ein zu konvertieren Stream:

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

(Nun, es ist eine ziemlich lange Schlange.)

Wenn Sie nicht mit Java 9 arbeiten, können Sie das manuell mithilfe der in Holgers Antwort angegebenen Technik Enumerationin eine konvertieren .Iterator

Stuart Marks
quelle
23
Wir können fast alles zu einem Einzeiler machen, wenn wir die Linie lang genug machen; ^)
Holger
12

Laut Guava-Dokumenten können Sie die folgende Iterators.forEnumeration()Methode verwenden:

Enumeration<Something> enumeration = ...;

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

In dieser Frage wird erklärt, wie Sie einen Stream von einem Iterator erhalten:

Stream<Something> stream = StreamSupport.stream(
    Spliterators.spliteratorUnknownSize(
        iterator, Spliterator.ORDERED),
    false);
fps
quelle
Obwohl es in diesem Fall funktionieren kann, ist dieses iterable nicht wirklich ein Iterable: Sie können es genau einmal iterieren!
dfogni
@dfogni Das gleiche passiert mit Streams. Verwenden und verwerfen, sagen sie :)
fps
Im Kontext ist es vollkommen in Ordnung, aber es ist ein Fehler, der darauf wartet, passiert zu werden, weil es ein Objekt mit einer anderen Semantik deklariert als die Schnittstelle sagt. Noch wichtiger ist, dass es meiner Meinung nach ein Missbrauch der Iterable-Schnittstelle ist, die zufällig von einem Lambda zugewiesen werden kann, aber kein @FunctionalInterface sein soll
dfogni
6

In meiner StreamEx- Bibliothek gibt es eine einfache Methode, StreamEx.of(Enumeration)die den Job erledigt:

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

Beachten Sie, dass dies nicht nur eine Verknüpfung zur @ Holger-Lösung ist, sondern auf unterschiedliche Weise implementiert wird. Insbesondere weist es im Vergleich zu Lösungen, an denen beteiligt ist, signifikant bessere parallele Ausführungseigenschaften auf Spliterators.spliteratorUnknownSize().

Tagir Valeev
quelle