Sich einen Iterator von einem Stream geben zu lassen ist nützlich, weil dann der Zeitpunkt des Konsumierens vom Zeitpunkt des Stream-Aufbaus getrennt werden kann. Es ist nicht einfach mehrere Streams gleichzeitig abzulaufen, doch mit Iteratoren funktioniert das. Zudem lässt sich in Stream mit einer Generator-Funktion einfach aufbauen, in Iterator aber nicht.
Beispiel: Aus zwei Streams sollen jeweils alterrnierend das nächste Element konsumiert und ausgegeben werden.
Iterator<Integer> iterator1 = Stream.of( 1, 3, 5 ).iterator(); Iterator<Integer> iterator2 = Stream.of( 2, 4, 6, 7, 8 ).iterator(); while ( iterator1.hasNext() || iterator2.hasNext() ) { if ( iterator1.hasNext() ) System.out.println( iterator1.next() ); if ( iterator2.hasNext() ) System.out.println( iterator2.next() ); }
Iterator ist ein Datentyp, der häufig in der Rückgabe verwendet wird, seltener als Parametertyp. Mit stream.iterator() ist es aber möglich, die Daten vom Stream genau an solchen Stellen zu übergeben. Einen Stream in einen Iterator zu konvertieren, um diesen dann mit hasNext()/next() abzulaufen, ist wenig sinnvoll, hierfür bietet sich genauso gut forEach(…) auf dem Stream an.
Stream ist nicht Iterable
Der Typ Stream bietet eine Methode iterator(), erweitert jedoch die Schnittstelle Iterable nicht. In der Javadoc ist bei iterator() die Bemerkung „terminale Operation“ vermerkt, denn der Iterator saugt den Stream leer, sodass ein zweiter iterator()-Aufruf auf einem Stream nicht möglich ist. Bei Klassen, die Iterable implementieren, muss ein Aufruf von iterator() beliebig oft möglich sein. Bei Streams ist das nicht gegeben, da die Streams selbst nicht für die Daten stehen wie eine Collection, die daher Iterable ist.
Iterator in Stream umwandeln
Ist auf der anderen Seite ein Iterator gegeben, lässt sich dieser nicht direkt in einen Stream bringen. Iteratoren können wie Streams unendlich sein, und es gibt keinen Weg, die Daten eines Iterators als Quelle zu benutzen. Natürlich ist es möglich, den Iterator abzulaufen und daraus einen neuen Stream aufzubauen, dann muss der Iterator aber endlich sein.
Beispiel: Die Methode ImageIO.getImageReadersBySuffix(String) liefert einen Iterator von ImageReader-Objekten – sie sollen über einen Strom zugänglich sein:
Builder<ImageReader> builder = Stream.builder(); for ( Iterator<ImageReader> iter = ImageIO.getImageReadersBySuffix( "jpg" ); iter.hasNext(); ) builder.add( iter.next() ); Stream<ImageReader> stream = builder.build(); System.out.println( stream.count() ); // 1
Einen anderen Weg geht StreamSupport.
Ist eine alte Enumeration gegeben, hilft Collections.list(enumeration).stream(), denn list(…) liefert eine ArrayList mit allen Einträgen; list(Iterator) gibt es da hingegen nicht.
iterator() von BaseStream und PrimitiveIterator.OfXXX
Die Schnittstelle Stream deklariert keine abstrakte iterator()-Methode, sondern bekommt die Methode vom Obertyp BaseStream vererbt. BaseStream ist insgesamt Basistyp von:
- Stream<T>, DoubleStream, IntStream, LongStream
Alle diese drei Typen haben damit iterator()-Methdoden, doch die Rückgaben sind unterschiedlich:
Typ | iterator()-Methdoden und Rückgabe |
Stream<T> | Iterator<T> iterator() |
DoubleStream | PrimitiveIterator.OfDouble iterator() |
IntStream | PrimitiveIterator.OfInt iterator() |
LongStream | PrimitiveIterator.OfInt iterator() |
Unterschiedliche Rückgaben der Iteratoren
PrimitiveIterator ist eine Schnittstelle aus dem Paket java.util mit eben drei inneren statischen Schnittstellen: OfDouble, OfInt und OfLong, die PrimitiveIterator erweiten. Jeder dieser drei inneren Typen hat eigene, aber symmetrische Methoden:
- default void forEachRemaining(Consumer<? super WrapperTyp> action)
- default void forEachRemaining(WrapperTypConsumer action)
- default WrapperTyp next()
- PrimitiverTyp nextPrimitiverTyp()
Statt WrapperTyp und PrimitiverTyp ist dann Double, Integer, Long und double, int, double einzusetzen.
Beispiel: Ein vom Stream abgeleiter Iterator besorgt Zahlen. Diese werden in einem anderen Stream eingesetzt.
PrimitiveIterator.OfInt counter = IntStream.iterate( 1, Math::incrementExact ).iterator(); Stream.of( "Telegram", "WhatsApp", "Facebook Messenger", "Insta" ) .map( s -> counter.nextInt() + ". " + s ) .forEach( System.out::println );