Как превратить перечисление Java в поток?

У меня есть сторонняя библиотека, которая дает мне Enumeration<String>. Я хочу работать с этим перечислением лениво, как Java 8 Stream, вызывая на нем такие вещи, как filter, map и flatMap.

Есть ли в нем существующая библиотека? Я уже ссылаюсь на Guava и Apache Commons, поэтому, если у любого из них есть решение, которое было бы идеальным.

Альтернативно, какой лучший/самый простой способ превратить Enumeration в Stream, сохраняя ленивый характер всего?

Ответ 1

Этот ответ уже предоставляет решение, которое создает Stream из 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);
 }

Следует подчеркнуть, что результирующий Stream является таким же ленивым, как и любой другой Stream, поскольку он не будет обрабатывать какие-либо элементы до того, как действие терминала будет начато, и, если операция терминала имеет короткое замыкание, он будет повторять только столько элементов, сколько необходимо.

Тем не менее, у него есть возможности для улучшения. forEachRemaining всегда добавляю метод forEachRemaining когда существует простой способ обработки всех элементов. Указанный метод будет вызываться реализацией Stream для большинства операций без короткого замыкания:

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

Однако приведенный выше код является жертвой антипаттерна "использующий Iterator потому что он так знаком". Созданный Iterator будет Spliterator в реализацию нового интерфейса Spliterator и не дает никаких преимуществ по Spliterator прямой реализацией Spliterator:

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

На уровне исходного кода эта реализация так же проста, как Iterator -based, но исключает делегирование Spliterator Iterator. Это только требует, чтобы его читатели узнали о новом API.

Ответ 2

Почему бы не использовать ванильную Java:

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

Однако, как упоминалось @MicahZoltu, необходимо учитывать количество элементов в перечислении, так как Collections.list сначала перебирает нумерацию для копирования элементов в ArrayList. Оттуда можно использовать обычный stream. Хотя это обычно для многих операций потока сбора, если перечисление слишком велико (например, бесконечное), это может вызвать проблему, потому что перечисление должно быть преобразовано в список, тогда вместо этого следует использовать другие описанные здесь подходы.

Ответ 3

В Java 9 можно преобразовать Enumeration в Stream с помощью одной строки:

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

(Ну, это довольно длинная очередь.)

Если вы не используете Java 9, вы можете вручную преобразовать Enumeration в Iterator используя технику, приведенную в ответе Хольгера.

Ответ 4

Согласно документации по Guava, вы можете использовать метод Iterators.forEnumeration():

Enumeration<Something> enumeration = ...;

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

И в этом вопросе объясняется, как получить поток от итератора:

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

Ответ 5

В моей библиотеке StreamEx есть простой метод StreamEx.of(Enumeration) который выполняет задание:

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

Обратите внимание, что это не просто ярлык для решения @Holger, но реализован по-разному. В частности, он имеет значительно лучшие характеристики параллельного исполнения по сравнению с решениями, использующими Spliterators.spliteratorUnknownSize().