17.4 Einen Stream erzeugen
Das Paket java.util.stream deklariert diverse Typen rund um Streams. Im Zentrum steht für Objektströme eine generisch deklarierte Schnittstelle Stream. Ein konkretes Exemplar wird immer von einer Datenquelle erzeugt. Unter anderem stehen folgende Stream-Erzeuger zur Verfügung:
Methode | Rückgabe | |
---|---|---|
Collection<E> | stream() | Stream<E> |
Arrays | stream(T[] array) (statisch) | Stream<T> |
stream(T[] array, int start, int end) (statisch) | Stream<T> | |
Stream | empty() (statisch) | Stream<T> |
of(T... values) (statisch) | Stream<T> | |
of(T value) (statisch) | Stream<T> | |
generate(Supplier<T> s)(statisch) | Stream<T> | |
iterate(T seed, | Stream<T> | |
iterate(T seed, Predicate<? super T> hasNext, UnaryOperator<T> next) (statisch) | Stream<T> | |
ofNullable(T t) (statisch) | Stream<T> | |
Optional<T> | Stream() | Stream<T> |
Scanner | tokens() (ab Java 9) | Stream<String> |
String | lines() (ab Java 11) | Stream<String> |
Files | lines(Path path) | Stream<String> |
lines(Path path, Charset cs) | Stream<String> | |
list(Path dir) | Stream<Path> | |
walk(Path start, FileVisitOption... options) | Stream<Path> | |
walk(Path start, int maxDepth, FileVisitOption... options) | Stream<Path> | |
find(Path start, int maxDepth, BiPredicate<Path,BasicFileAttributes> matcher, FileVisitOption... options) | Stream<Path> | |
BufferedReader | lines() | Stream<String> |
Pattern | splitAsStream(CharSequence input) | Stream<String> |
stream() | Stream<? extends ZipEntry> | |
JarFile | stream() | Stream<JarEntry> |
versionedStream() (ab Java 10) | Stream<JarEntry> |
Die Methode Stream.empty() liefert wie erwartet einen Strom ohne Elemente.
Stream.ofXXX(…)
Stream hat einige Fabrikmethoden für neue Ströme (etwa of(…)), alles andere sind Objektmethoden anderer Klassen, die Ströme liefern. Die of(…)-Methoden sind als statische Schnittstellenmethoden von Stream implementiert, und of(T... values) ist nur eine Fassade für Arrays.stream(values). Die Methode static <T> Stream<T> ofNullable(T t) liefert, wenn t == null ist, einen leeren Stream; und wenn t != null ist, liefert sie einen Stream mit genau dem Element t. Die Methode ist nützlich für Teilströme, die integriert werden.
[zB] Beispiel
Produziere einen Stream aus gegebenen Ganzzahlen, entferne die Vorzeichen, sortiere das Ergebnis und gibt es aus:
Stream.of( -4, 1, -2, 3 )
.map( Math::abs )
.sorted()
.forEach( System.out::println ); // 1 2 3 4
Stream.generate(…)
generate(…) produziert Elemente aus einem Supplier; der Stream ist unendlich.
[zB] Beispiele
Erzeuge ein Array von zehn Zufallszahlen nach Normalverteilung:
Random random = new Random();
double[] randoms = Stream.generate( random::nextGaussian )
.limit( 10 ).mapToDouble( e -> e ).toArray();
System.out.println( Arrays.toString( randoms ) );
Erzeuge einen Strom von Fibonacci-Zahlen[ 254 ](https://de.wikipedia.org/wiki/Fibonacci-Folge):
class FibSupplier implements Supplier<BigInteger> {
private final Queue<BigInteger> fibs =
new LinkedList<>( Arrays.asList( BigInteger.ZERO, BigInteger.ONE ) );
@Override public BigInteger get() {
fibs.offer( fibs.remove().add( fibs.peek() ) );
return fibs.peek();
}
};
Stream.generate( new FibSupplier() )
.limit( 1000 ).forEach( System.out::println );
Die Implementierung nutzt keinen Lambda-Ausdruck, sondern eine altmodische Klassenimplementierung, da wir uns für die Fibonacci-Folgen die letzten beiden Elemente merken müssen. Sie speichert eine LinkedList, die als Queue genutzt wird. Bei der Anfrage an ein neues Element nehmen wir das erste Element heraus, sodass das zweite nachrutscht, und addieren es mit dem Kopfelement, was die Fibonacci-Zahl ergibt. Die hängen wir im zweiten Schritt hinten an und geben sie zurück. Parallele Zugriffe sind nicht gestattet.
Stream.iterate(…)
Die zwei statischen iterate(…)-Methoden generieren einen Stream aus einem Startwert und einer Funktion, die das nächste Element produziert. Bei iterate(T seed, UnaryOperator<T> f) ist der Strom unendlich, bei der zweiten (in Java 9 hinzugekommenen) Methode iterate(…, Predicate<? super T> hasNext, …) beendet ein erfülltes Prädikat den Strom und erinnert an eine klassische for-Schleife. Der Abbruch über ein Prädikat ist sehr flexibel, denn bei der ersten iterate(…)-Methode ist der Strom unendlich, und so folgt oftmals ein limit(…) oder takeWhile(…) zum Limitieren der Elemente.
[zB] Beispiel 1
Produziere Permutationen eines Strings.
UnaryOperator<String> shuffleOp = s -> {
char[] chars = s.toCharArray();
for ( int index = chars.length - 1; index > 0; index-- ) {
int rndIndex = ThreadLocalRandom.current().nextInt( index + 1 );
if ( index == rndIndex ) continue;
char c = chars[ rndIndex ];
chars[ rndIndex ] = chars[ index ];
chars[ index ] = c;
}
return new String( chars );
};
String text = "Sie müssen nur den Nippel durch die Lasche ziehn";
Stream.iterate( text, shuffleOp ).limit( 10 ).forEach( System.out::println );
Die Ganzahl-Zufallszahlen stammen dies Mal nicht von einen Random-Objekt, sondern von ThreadLocalRandom. Diese spezielle Klasse ist mit dem aktuellen Thread verbunden und kann ebenfalls einzelne Zufallszahlen oder einen Strom liefern. Bei Nebenläufigkeit bietet diese Variante eine bessere Performance.
[zB] Beispiel 2
Erzeuge einen endlosen Stream aus BigInteger-Objekten, der bei 10.000.000 beginnt und in Einerschritten weitergeht, bis mit hoher Wahrscheinlichkeit eine Primzahl erscheint und der Strom damit endet:
Predicate<BigInteger> isNotPrime = i -> ! i.isProbablePrime( 10 );
UnaryOperator<BigInteger> incBigInt = i -> i.add( BigInteger.ONE );
Stream.iterate( BigInteger.valueOf( 10_000_000 ), isNotPrime, incBigInt )
.forEach( System.out::println );
17.4.1 Parallele oder sequenzielle Streams
Ein Stream kann parallel oder sequenziell sein. Das heißt, es ist möglich, dass Threads gewisse Operationen nebenläufig durchführen, wie zum Beispiel die Suche nach einem Element. Stream liefert über isParallel() die Rückgabe true, wenn ein Stream Operationen nebenläufig durchführt.
Alle Collection-Datenstrukturen können mit der Methode parallelStream() einen potenziell nebenläufigen Stream liefern.
Typ | Methode | Rückgabe |
---|---|---|
Collection | parallelStream() | Stream<E> |
Collection | stream() | Stream<E> |
Jeder parallele Stream lässt sich mithilfe der Stream-Methode sequential() in einen sequenziellen Stream konvertieren. Parallele Streams nutzen intern das Fork-&-Join-Framework, doch sollte nicht automatisch jeder Stream parallel sein, da das nicht zwingend zu einem Performance-Vorteil führt. Wenn zum Beispiel keine Parallelisierung möglich ist, bringt es wenig, Threads einzusetzen.