Jak przekonwertować iterator na strumień?


468

Szukam zwięzłego sposobu przekonwertowania pliku Iteratorna Streamlub bardziej szczegółowo, aby „wyświetlić” iterator jako strumień.

Ze względu na wydajność chciałbym uniknąć kopiowania iteratora na nowej liście:

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

W oparciu o niektóre sugestie w komentarzach próbowałem również użyć 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);
}

Dostaję jednak NoSuchElementException(ponieważ nie ma wywołania 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)

Patrzyłem StreamSupporti Collectionsnic nie znalazłem.



3
@DmitryGinzburg euh nie chcę tworzyć strumienia „Infinite”.
gontard

1
@DmitryGinzburg Stream.generate(iterator::next)działa?
gontard

1
@DmitryGinzburg To nie zadziała dla iteratora skończonego.
assylias

Odpowiedzi:


543

Jednym ze sposobów jest utworzenie Spliteratora z Iteratora i użycie go jako podstawy dla twojego strumienia:

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

Alternatywą, która może być bardziej czytelna, jest użycie Iterable - a tworzenie Iterable z Iteratora jest bardzo proste w lambdach, ponieważ Iterable to funkcjonalny interfejs:

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

Iterable<String> iterable = () -> sourceIterator;
Stream<String> targetStream = StreamSupport.stream(iterable.spliterator(), false);

26
Strumień jest leniwy: kod łączy tylko Strumień z Iteratorem, ale faktyczna iteracja nastąpi dopiero po operacji terminalu. Jeśli w międzyczasie użyjesz iteratora, nie uzyskasz oczekiwanego rezultatu. Na przykład możesz wprowadzić sourceIterator.next()przed użyciem strumienia, a zobaczysz efekt (pierwszy element nie będzie widoczny w strumieniu).
assylias

9
@assylias, tak, to naprawdę miłe! Może mógłbyś wyjaśnić przyszłym czytelnikom tę dość magiczną linię Iterable<String> iterable = () -> sourceIterator;. Muszę przyznać, że zrozumienie zajęło mi trochę czasu.
gontard

7
Powinienem powiedzieć, co znalazłem. Iterable<T>to FunctionalInterfacemetoda, która ma tylko jedną metodę abstrakcyjną iterator(). Podobnie () -> sourceIteratorjak wyrażenie lambda, które tworzy Iterableinstancję jako anonimową implementację.
Jin Kwon

13
Ponownie, () -> sourceIterator;jest skróconą formąnew Iterable<>() { @Override public Iterator<String> iterator() { return sourceIterator; } }
Jin Kwon

7
@ JinKwon To nie jest tak naprawdę skrócona forma anonimowej klasy (istnieje kilka subtelnych różnic, takich jak zakres i sposób kompilacji), ale w tym przypadku zachowuje się podobnie.
assylias,

122

Od wersji 21 biblioteka Guava zapewnia Streams.stream(iterator)

To co robi @ assylias „s odpowiedź pokazy .



O wiele lepiej używać tego konsekwentnie, dopóki JDK nie będzie obsługiwać natywnej wersji jednowierszowej. W przyszłości będzie o wiele łatwiej znaleźć (stąd refaktoryzować) niż rozwiązania czysto JDK pokazane gdzie indziej.
drekbour

Jest to doskonałe, ale… w jaki sposób Java ma natywne iteratory i strumienie… ale nie ma wbudowanej, prostej drogi do przejścia z jednej na drugą !? Moim zdaniem całkiem pominięciem.
Dan Lenski

92

Świetna sugestia! Oto moje zdanie na ten temat:

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

I użycie (pamiętaj, aby zaimportować statycznie asStream):

List<String> aPrefixedStrings = asStream(sourceIterator)
                .filter(t -> t.startsWith("A"))
                .collect(toList());

43

Jest to możliwe w Javie 9.

Stream.generate(() -> null)
    .takeWhile(x -> iterator.hasNext())
    .map(n -> iterator.next())
    .forEach(System.out::println);

1
Prosty, wydajny i bez uciekania się do podklasy - taka powinna być zaakceptowana odpowiedź!
martyglaubitz,

1
Niestety nie działają one ze .parallel()strumieniami. Wydają się również nieco wolniejsze niż przejście Spliterator, nawet w przypadku użycia sekwencyjnego.
Thomas Ahle,

Również pierwsza metoda wyrzuca się, jeśli iterator jest pusty. Druga metoda na razie działa, ale narusza wymóg funkcji w mapach i przyjmuje, że jest bezstanowy, więc wahałbym się zrobić to w kodzie produkcyjnym.
Hans-Peter Störr

Naprawdę powinna to być zaakceptowana odpowiedź. Choć parallelmoże być funky, prostota jest niesamowita.
Sven

11

Klasa Create Spliteratorfrom Iteratorusing Spliteratorszawiera więcej niż jedną funkcję do tworzenia spliteratora, na przykład tutaj używam, spliteratorUnknownSizektóry pobiera iterator jako parametr, a następnie tworzę Stream za pomocąStreamSupport

Spliterator<Model> spliterator = Spliterators.spliteratorUnknownSize(
        iterator, Spliterator.NONNULL);
Stream<Model> stream = StreamSupport.stream(spliterator, false);

1
import com.google.common.collect.Streams;

i użyj Streams.stream(iterator):

Streams.stream(iterator)
       .map(v-> function(v))
       .collect(Collectors.toList());

-4

Posługiwać się Collections.list(iterator).stream()...


6
Choć jest krótki, jest to bardzo słabe wyniki.
Olivier Grégoire

2
Spowoduje to rozwinięcie całego iteratora do obiektu Java, a następnie konwersję do strumienia. Nie sugeruję tego
iec2011007

3
Wydaje się, że dotyczy to tylko wyliczeń, a nie iteratorów.
john16384

1
Ogólnie rzecz biorąc, nie jest to okropna odpowiedź, przydatna w szczypcie, ale pytanie wspomina o wydajności, a odpowiedź nie jest skuteczna.
Sanki
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.