© DrHitch/Shutterstock.com
Java 9 Streams

3 Streams - Core Methods


Nachdem wir uns im vorigen Kapitel damit beschäftigt haben, wie die Daten in die Streams gelangen und wie sie wieder zu entnehmen sind, werden wir uns jetzt mit der Datentransformation beschäftigen. Es stehen unter anderem drei Basismethoden – forEach, match und find – zur Verfügung, mit denen man schnell und einfach die ersten Versuche unternehmen kann.

3.1 forEach – ein Lambda für jedes Element

Die Methode forEach(<lambda>) macht eigentlich genau das, was man vermutet: Sie wendet das als Argument übergebene Lambda auf jedes einzelne Element des Streams an. Diese Methode ist auch bei Iterable, List, Map und einigen anderen Klassen/Interfaces zu finden, was erfreulicherweise zu kürzeren sprachlichen Konstrukten führt. Listing 3.1 zeigt zuerst die Iteration in Pre JDK 8 Notation und danach unter Verwendung von forEach(<lambda>). In ihm sind lange und kurze Versionen zu sehen. Die langen Versionen verwenden die volle Notation inklusive der geschwungenen Klammern. Jeweils zuletzt wird eine Version mit der Verwendung von Method References angegeben, zu erkennen an den Doppelpunkten.

final List<Integer> list = List.of(1 , 2 , 3 , 4);

for (Integer integer : list) {
System.out.println("integer = " + integer);
}

list.stream().forEach((Integer integer) -> {System.out.println(integer);});
list.stream().forEach(integer -> {System.out.println(integer);});
list.stream().forEach(integer -> System.out.println(integer));
list.stream().forEach(System.out::println);

list.parallelStream().forEach((Integer integer) -> {System.out.println(integer);});
list.parallelStream().forEach(integer -> {System.out.println(integer);});
list.parallelStream().forEach(integer -> System.out.println(integer));
list.parallelStream().forEach(System.out::println);

list.forEach((Integer integer) -> {System.out.println(integer);});
list.forEach(integer -> {System.out.println(integer);});
list.forEach(integer -> System.out.println(integer));
list.forEach(System.out::println);

Listing 3.1

In Listing 3.1 besteht ein kleiner, aber feiner Unterschied zwischen den beiden Versionen. Die Variante, die den Stream mit der Methode parallelStream() erzeugt, lässt die Elemente parallel verarbeiten. Wir haben an der Stelle einen parallelen Stream.

Bei beiden ist sichergestellt, dass die übergebene Methodenreferenz nur einmal auf jedes Element angewandt wird. Nicht sichergestellt ist jedoch die Reihenfolge, in der die einzelnen Elemente abgearbeitet werden, wenn es sich um parallele Streams handelt. Ist die Reihenfolge von Bedeutung, muss die Methode forEachOrdered(<lambda>) verwendet werden. Hier wird bei der Abarbeitung die Reihenfolge, die in der Verarbeitungsstufe vor dem forEach(..) vorliegt, eingehalten. Diese Methode sollte nur verwendet werden, wenn es zwingend notwendig ist. Was hier allerdings auf den ersten Blick nicht ersichtlich ist: Es wird ein NotNullCheck durchgeführt, der im Fall von null mit einer NullPointerException quittiert wird. Jedes Element wird durch die consumer.accept-Methode konsumiert (Listing 3.2).

//class - ReferencePipeline
@Override
public void forEach(Consumer<? super P_OUT> action) {
evaluate(ForEachOps.makeRef(action, false));
}
//class - AbstractPipeline
final <R> R evaluate(TerminalOp<E_OUT, R> terminalOp) {
assert getOutputShape() == terminalOp.inputShape();
if (linkedOrConsumed)
throw new IllegalStateException(MSG_STREAM_LINKED);
linkedOrConsumed = true;
return isParallel()
? terminalOp.evaluateParallel(this,
sourceSpliterator(terminalOp.getOpFlags()))
: terminalOp.evaluateSequential(this,
sourceSpliterator(terminalOp.getOpFlags()));
}
//class - ForEachOps
public static <T> TerminalOp<T, Void>
makeRef(Consumer<? super T> action, boolean ordered) {
Objects.requireNonNull(action); //throws NPE
return new ForEachOp.OfRef<>(action, ordered);
}
//class – ForEachOps. OfRef
static final class OfRef<T> extends ForEachOp<T> {
final Consumer<? super T> consumer;
//...
@Override
public void accept(T t) {
consumer.accept(t);
}
}

Listing 3.2

Bei der Verwendung von forEach(<lambda>) ist auch Folgendes zu beachten: Durch die Methode accept im Consumer wird das Element konsumiert. Das bedeutet, dass forEach(<lambda>) nur einmal auf einen Stream angewandt werden kann. Man spricht in diesem Zusammenhang auch von einer terminalen Operation. Ist mehr als eine Operation auf das Element anzuwenden, kann dies natürlich innerhalb des übergebenden Lambdas geschehen. Das Argument der forEach(<lambda>)-Methode kann jedoch wiederverwendet werden, indem man eine Instanz vorhält und sie dann mehreren Streams übergibt (Listing 3.3).

final Consumer<? super Pair> consumer = System.out::println;
generateDemoValues.stream().forEachOrdered(consumer);
generateDemoValues.parallelStream().forEachOrdered(consumer);

Listing 3.3

Ebenfalls nicht gestattet ist die Manipulation von umgebenden Variablen. Wie das erfolgen kann, sehen wir uns im Zusammenhang mit den Methoden map und reduce an. Der größte Unterschied zu einer for-Schleife ist allerdings, dass nicht vorzeitig unterbrochen werden kann, weder mit break noch mit return.

3.2 map – Transformationen gefällig?

Die Methode map(<lambda>) erzeugt einen neuen Stream, bestehend aus der Summe aller Transformationen der Elemente des Quell-Streams. Auch hier ist das Argument wieder ein Lambda. Das bedeutet, dass der Ziel-Stream bis auf die funktionale Kopplung nichts mit dem Quell-Stream gemeinsam haben muss.

Im Beispiel in Listing 3.4 wird aus einem Stream<Integer> ein Stream<Float>, um anschließend in einen Stream<String> gemappt zu werden. Die Methode map(<lambda>) kann beliebig oft hintereinander angewandt werden, da das Ergebnis immer wieder ein neuer Stream ist.

final Stream<Integer> streamA = Stream.of(1 , 2 , 3 , 4 , 5);

final Stream<Float> streamB = streamA.map(new Function<Integer, Float>() {
@Override
public Float apply(Integer i) {
return Float.valueOf(i);
}
});

final Stream<String> streamC = strea...

Neugierig geworden? Wir haben diese Angebote für dich:

Angebote für Teams

Für Firmen haben wir individuelle Teamlizenzen. Wir erstellen Ihnen gerne ein passendes Angebot.

Das Library-Modell:
IP-Zugang

Das Company-Modell:
Domain-Zugang