Как преобразовать итератор в поток?

468

Я ищу краткий способ преобразовать Iteratorв Streamили более конкретно, чтобы «просмотреть» итератор в виде потока.

Из соображений производительности я бы хотел избежать копирования итератора в новый список:

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

Основываясь на некоторых предложениях в комментариях, я также попытался использовать 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);
}

Тем не менее, я получаю NoSuchElementException(так как нет вызова 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)

Я посмотрел StreamSupportи , Collectionsно я не нашел ничего.

Gontard
источник
5
Возможный дубликат Как создать бесконечный поток <E> из итератора <E>?
Дмитрий Гинзбург
3
@DmitryGinzburg euh Я не хочу создавать «Бесконечный» поток.
Gontard
1
@DmitryGinzburg Stream.generate(iterator::next)работает?
Gontard
1
@DmitryGinzburg Это не сработает для конечного итератора.
assylias
7
См. Stackoverflow.com/questions/23114015/…
Брайан Гетц

Ответы:

543

Один из способов - создать Spliterator из Iterator и использовать его в качестве основы для вашего потока:

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

Альтернатива, которая может быть более читабельна, - это использовать Iterable - и создание Iterable из Iterator очень просто с помощью лямбды, потому что Iterable - это функциональный интерфейс:

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

Iterable<String> iterable = () -> sourceIterator;
Stream<String> targetStream = StreamSupport.stream(iterable.spliterator(), false);
assylias
источник
26
Поток ленив: код только связывает Поток с Итератором, но фактическая итерация не произойдет, пока не завершится операция терминала. Если вы используете итератор, вы не получите ожидаемого результата. Например, вы можете ввести sourceIterator.next()перед использованием потока, и вы увидите эффект (первый элемент не будет виден потоком).
assylias
9
@assylias, да, это действительно хорошо! Возможно, вы могли бы объяснить будущим читателям эту волшебную линию Iterable<String> iterable = () -> sourceIterator;. Я должен признать, что мне потребовалось некоторое время, чтобы понять.
Гонтард
7
Я должен сказать, что я нашел. Iterable<T>это FunctionalInterfaceкоторый имеет только один абстрактный метод iterator(). Как () -> sourceIteratorи лямбда-выражение, создающее экземпляр Iterableэкземпляра как анонимная реализация.
Джин Квон
13
Опять же, () -> sourceIterator;это сокращенная формаnew Iterable<>() { @Override public Iterator<String> iterator() { return sourceIterator; } }
Джин Квон
7
@JinKwon На самом деле это не укороченная форма анонимного класса (есть несколько тонких различий, таких как область действия и способ его компиляции), но в этом случае он ведет себя аналогично.
assylias
122

Начиная с версии 21, библиотека Guava предоставляет Streams.stream(iterator)

Он делает то , что @ assylias «ы ответ показывает .

numéro6
источник
3
Javadoc: static.javadoc.io/com.google.guava/guava/21.0/com/google/common/…
Хенрик Аастед Серенсен,
Гораздо лучше использовать это последовательно до тех пор, пока JDK не поддерживает встроенную однострочную печать. В будущем будет гораздо проще найти (а значит, и реорганизовать) это, чем решения на чистом JDK, показанные в другом месте.
drekbour
Это прекрасно, но ... как у Java есть собственные итераторы и потоки ... но нет встроенного, простого способа перехода от одного к другому !? Довольно упущение на мой взгляд.
Дэн Ленски
92

Отличное предложение! Вот мой подход к повторному использованию:

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

И использование (не забудьте статически импортировать asStream):

List<String> aPrefixedStrings = asStream(sourceIterator)
                .filter(t -> t.startsWith("A"))
                .collect(toList());
Матан
источник
43

Это возможно в Java 9.

Stream.generate(() -> null)
    .takeWhile(x -> iterator.hasNext())
    .map(n -> iterator.next())
    .forEach(System.out::println);
PhilipRoman
источник
1
Простой, эффективный и не прибегающий к подклассам - это должен быть принятый ответ!
martyglaubitz
1
К сожалению, они не работают с .parallel()потоками. Они также выглядят немного медленнее, чем переход Spliterator, даже для последовательного использования.
Томас Але
Кроме того, первый метод бросает вызов, если итератор пуст. Второй метод работает на данный момент, но он нарушает требование функций в map и принимает состояние без сохранения состояния, поэтому я не решался бы сделать это в производственном коде.
Ганс-Петер Стёрр
Действительно, это должен быть принятый ответ. Даже при том, что это parallelможет быть забавно, простота удивительна.
Свен
11

Создание Spliteratorс Iteratorпомощью Spliteratorsкласса содержит более одной функции для создания spliterator, например , здесь я использую , spliteratorUnknownSizeкоторый получает итератор в качестве параметра, а затем создать поток с помощьюStreamSupport

Spliterator<Model> spliterator = Spliterators.spliteratorUnknownSize(
        iterator, Spliterator.NONNULL);
Stream<Model> stream = StreamSupport.stream(spliterator, false);
Bassem Reda Zohdy
источник
1
import com.google.common.collect.Streams;

и использовать Streams.stream(iterator):

Streams.stream(iterator)
       .map(v-> function(v))
       .collect(Collectors.toList());
снеха
источник
-4

использование Collections.list(iterator).stream()...

Израиль CS Rocha
источник
6
Хотя это коротко, это очень плохо.
Оливье Грегуар
2
Это развернет весь итератор в объект Java, а затем преобразует его в поток. Я не предлагаю это
iec2011007
3
Кажется, это только для перечислений, а не итераторов.
john16384
1
В общем, не страшный ответ, полезный в крайнем случае, но в вопросе упоминается производительность, а ответ бесполезен.
Sled