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


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но я не нашел ничего.



3
@DmitryGinzburg euh Я не хочу создавать «Бесконечный» поток.
Gontard

1
@DmitryGinzburg Stream.generate(iterator::next)работает?
Gontard

1
@DmitryGinzburg Это не сработает для конечного итератора.
assylias

Ответы:


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

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 «ы ответ показывает .



Гораздо лучше использовать это последовательно до тех пор, пока 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);

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

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()...


6
Хотя это коротко, это очень плохо.
Оливье Грегуар

2
Это развернет весь итератор в объект Java, а затем преобразует его в поток. Я не предлагаю это
iec2011007

3
Кажется, это только для перечислений, а не итераторов.
john16384

1
В общем, не страшный ответ, полезный в крайнем случае, но в вопросе упоминается производительность, а ответ бесполезен.
Sled
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.