Java 8: IntStream – der Stream-Macher
Java 8 hat vier unterschiedliche Schnittstellen für Streams eingeführt: Die generische Schnittstelle Stream<T>
für Referenztypen, sowie die drei Schnittstellen IntStream
, LongStream
und DoubleStream
für primitive Typen. Doch eine dieser Schnittstellen ist besonders: Was die Zählschleife unter den Schleifen ist, ist der IntStream
unter den Streams. Es folgen fünf Beispiele für die Verwendung.
Bezeichnend
Üblicherweise verwendet man einen Stream über einen Typen T
, um Auswertungen über Elemente dieses Typs zu machen. Eine Entsprechung für int
s ist im folgenden Listing zu sehen. Dabei wird der Stream verwendet, um die Summe der Ganzzahlen aus dem angegebenen, halboffenen Interval zu bestimmen, die eine gewisse Eigenschaft erfüllen.
int evenSum(int fromInclusive, int toExclusive) {
return IntStream.range(fromInclusive, toExclusive)
.filter(i -> i % 3 != 0)
.sum();
}
Die Implementierung entspricht also im Wesentlichen einer normalen for
-Schleife mit einer verschachtelten if
-Bedingung.
Indizierend
Andererseits lassen sich zählende for
-Schleifen auch verwenden, um mittels Index über die Elemente eines Arrays zu iterieren. Analog dazu kann man ein IntStream
verwenden, um aus einem Array einen Stream zu erzeugen. Eine entsprechende Implementierung findet sich im folgenden Listing.
<T> Stream<T> makeStream(T[] array) {
return IntStream.range(0, array.length)
.mapToObj(i -> array[i]);
}
Für den produktiven Einsatz ist dieser Einzeiler bereits qualitativ hinreichend. Man könnte sie sogar empfehlen, wenn es mit java.util.Arrays::stream
nicht bereits eine geeignetere Entsprechung in der Standardbibliothek gäbe.
Skalierend
In den beiden vorherigen Beispielen wurde jeweils die Methode IntStream::range
verwendet, um einen Stream sequentiell aufsteigender int
s zu erzeugen. Für DoubleStream
gibt es dazu keine Entsprechung. Doch mit Hilfe von IntStream
lässt sich einfach eine dazu konstruieren, wie im folgenden Listing zu sehen.
DoubleStream range(int from, int to, double scale) {
return IntStream.range(from, to)
.mapToDouble(i -> i * scale);
}
Der Aufruf range(0, 100, 0.01)
liefert somit einen DoubleStream
, der in Prozent-Schritten von 0.0
bis 1.0
(exklusive) läuft.
Kombinierend
Häufig werden Streams nur mit Elementen aus einer einzelnen Quelle verwendet. Doch was tun, wenn mehrere Quellen miteinander kombiniert werden müssen? Die Standardbibliothek von Java 8 bietet dafür direkt keine Lösung an. Doch auch hier kann der IntStream
helfen. Das folgende Listing zeigt, wie man IntStream
verwenden kann, um das Skalarprodukt zweier Vektoren im kartesischen Koordinatensystem zu berechnen.
double innerProduct(double[] lhs, double[] rhs) {
int length = Math.max(lhs.length, rhs.length);
return IntStream.range(0, length)
.mapToDouble(i -> lhs[i] * rhs[i])
.sum();
}
Das Ganze funktioniert auch für andere Element- und Collection-Typen. Für die Container ist nur wichtig, dass sie einen effizienten Zugriff mittels Index unterstützten – wie beispielsweise bei der ArrayList
.
Konvertierend
Spezifische Schnittstellen für Streams existieren nur für die primitiven Typen int
, long
und double
. Für byte
, short
, char
und float
muss man dagegen auf irgendeine Art von Konvertierung zurückgreifen. Im folgenden Listing ist eine Implementierung für float
-Arrays zu sehen, die jedoch nur auf den ersten Blick gut aussieht.
Stream<Float> makeStream(float[] array) {
return IntStream.range(0, array.length)
.mapToObj(i -> array[i]);
}
Das Problem hierbei ist, dass für jedes Element potentiell ein neues Float
-Objekt erzeugt wird. Das verursacht sowohl Overhead bei der Erzeugung als auch bei der Garbage-Collection. Besser wäre das float
-Array zunächst in ein double
-Array zu kopieren und daraus einen DoubleStream
zu erzeugen. Noch besser ist allerdings die Konvertierung on-the-fly mit Hilfe eines IntStream
durchzuführen, wie im folgenden Listing zu sehen.
DoubleStream makeStream(float[] array) {
return IntStream.range(0, array.length)
.mapToDouble(i -> array[i]);
}
Der Trick besteht also wiederum darin, den IntStream
zur Indizierung zu verwenden. In gewisser Weise handelt es sich hierbei um ein wiederkehrendes Muster – sowie die Zählschleife ein Muster der allgemeinen Schleife.