Як перетворити ітератор в потік?


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 Так, я не хочу створювати "Безмежний" потік.
gontard

1
@DmitryGinzburg Stream.generate(iterator::next)працює?
gontard

1
@DmitryGinzburg Це не спрацює для обмеженого ітератора.
assylias

Відповіді:


543

Один із способів - створити Spliterator з Ітератора та використовувати його як основу для вашого потоку:

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
Потік ледачий: код пов'язує лише Потік до Ітератора, але фактична ітерація не відбудеться до операції з терміналом. Якщо ви тим часом використовуєте ітератор, ви не отримаєте очікуваного результату. Наприклад, ви можете ввести a sourceIterator.next()перед використанням потоку, і ви побачите ефект (перший елемент не буде видно потоком).
assylias

9
@assylias, так, це дійсно приємно! Можливо, ви могли б пояснити для майбутніх читачів цю досить чарівну лінію Iterable<String> iterable = () -> sourceIterator;. Я мушу визнати, що мені знадобилося певний час, щоб зрозуміти.
gontard

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, навіть для послідовного використання.
Томас Ейл

Також перший метод підводиться, якщо ітератор порожній. Другий метод наразі працює, але він порушує вимогу функцій у карті та вважає, щоби він був без громадянства, тому я б вагався з цим у виробничому коді.
Ганс-Пітер Штерр

Дійсно, це має бути прийнятою відповіддю. Незважаючи на те, що це parallelможе бути прикольно, простота вражає.
Свен

11

Створити Spliteratorза Iteratorдопомогою Spliteratorsкласу містить більше однієї функції для створення spliterator, наприклад, тут я використовую, spliteratorUnknownSizeщо отримує ітератор як параметр, а потім створює Stream за допомогою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
Не страшна відповідь загалом, корисна в крайній мірі, але питання зазначає продуктивність, а відповідь не виконує.
Санки
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.