Geht das nicht streamiger?

Zu Beginn meiner Ausbildung habe ich in meinen Code, nach einigen Code-Reviews durch meinen Ausbilder Stefan oder Azubi-Kollegen Jonas, immer mal wieder solch kuriose TODOs vorgefunden:

Jetzt fragst du dich sicherlich, was denn an diesen mega geilen Enhanced-For noch auszusetzen ist oder was dieses „streamiger“ zu bedeuten hat. Das gleiche habe ich mich natürlich auch gefragt und ein Tutorial von Oracle dazu durchgearbeitet. An einigen Stellen fand ich das nicht sehr verständlich, deshalb werde ich jetzt mal versuchen die Erkenntnisse, die ich mir angeeignet habe, vereinfacht und anschaulich wiederzugeben. Sozusagen von Java-Anfänger für Java-Anfänger. 😀

Was sind Lambdas und Streams?

Anfang des Jahres 2014 kam Java 8 raus mit einigen Neuerungen. Unter anderem wurden die sogenannten Lambdas und Streams eingeführt. Ein Stream ist kurz gesagt, ein Strom von Elementen und durch Lambda-Ausdrücke ist es möglich, anonyme Funktionen ohne großen „syntaktischen Overhead“ zu definieren. Solche Kürzungen der Syntax bezeichnet man auch als „syntaktischer Zucker“. Aber eins nach dem anderen.

Lambdas

Wir schauen uns erstmal anhand einer anonymen Funktion an, wie genau Lambda-Ausdrücke unseren Code „versüßen“. Beim Folgenden Code habe ich mich mal des JavaFX Hello World Beispiels von Oracle bedient:

Sieht erstmal wild aus aber eigentlich ist das nur der Code für einen Button der auf der Konsole „Hello World!“ ausgeben soll, sobald er gedrückt wird.

Dafür, dass uns nur die Ausgabe auf der Konsole interessiert, sieht das ganz schön ekelhaft aus oder?

Aber genau dafür haben wir ja jetzt unsere Lambdas. 😀

Der Lambda-Ausdruck erlaubt uns die komplette Methodendefinition wegzulassen, weil der Compiler das jetzt für uns macht. Doch das ist nicht das einzige was er für uns erledigen kann. Den Datentyp (final ActionEvent event) kann der Compiler für uns aus dem Kontext herleiten. Sowas nennt man auch „Typinferenz“. Das bringt uns dann auch zu unseren fertigen Lambda-Einzeiler:

Streams

Dieses Lambda-Zeugs sieht jetzt bei diesen Beispiel zwar ganz geil aus aber solange wir keine hardcore JavaFX Entwickler sind hilft uns das jetzt erstmal nich wirklich weiter. Das Potential der Lambdas wird erst im Zusammenhang mit der Streams API richtig ausgeschöpft.

Einen Stream kannst du dir vorstellen wie einen Fluss in dem mehrere Elemente, wie zum Beispiel ein paar Fische, Quallen und Krebse schwimmen. Die Streams API stellt nun mehrere Methoden bereit mit denen wir an unseren Fluss arbeiten können. Wir könnten beispielsweise ein Netzauswerfen, was den Beifang rausfiltert, dann die Fische alle verpacken und in einen Laster verladen, der die Fischladungen zum Händler fährt.


Image courtesy of toptal.com

Um ein wenig in den technischen Sprachgebrauch zurückzukehren: Wir filtern nach den Fischen. Wenn wir die Fische dann alle in unseren Stream haben, wenden wir eine Methode auf sie an, nämlich das verpacken. Und wenn alles verpackt ist, sammeln wir die verpackten Fische ein. Damit haben wir quasi schon drei der typischen „streamigen“ Methoden benutzt: filter(), map() und collect().
Wichtig hierbei ist, dass erst durch die collect-Methode der Stream beendet wurde. Das liegt daran, weil die Methoden die wir benutzen in intermediate und terminal Operations, also Zwischen- und Endoperation unterteilt sind. Zu beachten ist außerdem, dass die Zwischenoperationen erst ausgeführt werden, sobald eine Endoperation folgt. Sie sind also „faul“ bzw. „lazy“. Der Begriff kommt überigens auch an anderen Stellen in der Softwareentwicklung vor. 🙂

Wir haben nun einiges über Lambdas und Streams gelernt und gucken jetzt mal ob wir unseren Code zu Beginn des Posts nicht streamiger machen können. 😀

Wir haben hier eine Liste von Personen, wovon wir die Volljährigen auf der Konsole ausgeben wollen. Die Klasse Person besteht aus den Attributen Vorname, Nachname und Alter und implementiert neben den Gettern und Settern eine toString-Methode, die den Vor- und Nachnamen auf der Konsole ausgibt. So eine Java Klasse, die nur ein paar Attribute und vielleicht noch Implementierungen von der hashCode-, equals- oder toString-Methode beinhaltet, nennt man auch „Bean“. Die Liste der Personen sieht foldendermaßen aus:

Wir wollen also alle Namen außer Felix auf der Kommandozeile sehen. Das Funktioniert mit unserem Enhanced-For auch schon, durch Streams können wir aber noch ein wenig Code einsparen.

Dazu brauchen wir erstmal unseren Stream bzw. Fluss. Das geht überraschend einfach mit personen.stream()
Diesen Stream wollen wir jetzt nach Personen über oder gleich 18 Jahren filtern.

Jetzt brauchen wir noch eine Methode, die quasi über alle Elemente drüberläuft und unser System.out.println(person) ausführt. Dafür bietet die Streams API die Methode „forEach“ an. Die ist außerdem eine Endoperation, sodass unser Stream auch loslegt.

Und schon ist unser Code richtig schön streamig. 😀

Es gibt allerdings noch eine weitere Kleinigkeit die wir verbessern können. Dazu brauchen wir noch mehr syntaktischen Zucker: Method Referencing. Den gibt es aber erst im nächsten Post. 😀

Ich hoffe, das Ganze hat sich leicht und verständlich gelesen. Wenn du noch ein paar Verbesserungsvorschläge oder Anmerkungen hast, dann lass gerne einen Kommentar da. 🙂