Rheinwerk Computing < openbook >


 
Inhaltsverzeichnis
Materialien zum Buch
Vorwort
1 Java ist auch eine Sprache
2 Imperative Sprachkonzepte
3 Klassen und Objekte
4 Arrays und ihre Anwendungen
5 Der Umgang mit Zeichen und Zeichenketten
6 Eigene Klassen schreiben
7 Objektorientierte Beziehungsfragen
8 Schnittstellen, Aufzählungen, versiegelte Klassen, Records
9 Ausnahmen müssen sein
10 Geschachtelte Typen
11 Besondere Typen der Java SE
12 Generics<T>
13 Lambda-Ausdrücke und funktionale Programmierung
14 Architektur, Design und angewandte Objektorientierung
15 Java Platform Module System
16 Die Klassenbibliothek
17 Einführung in die nebenläufige Programmierung
18 Einführung in Datenstrukturen und Algorithmen
19 Einführung in grafische Oberflächen
20 Einführung in Dateien und Datenströme
21 Einführung ins Datenbankmanagement mit JDBC
22 Bits und Bytes, Mathematisches und Geld
23 Testen mit JUnit
24 Die Werkzeuge des JDK
A Java SE-Module und Paketübersicht
Stichwortverzeichnis


Buch bestellen
Ihre Meinung?



Spacer
<< zurück
Java ist auch eine Insel von Christian Ullenboom

Einführung, Ausbildung, Praxis
Buch: Java ist auch eine Insel


Java ist auch eine Insel

Pfeil 18 Einführung in Datenstrukturen und Algorithmen
Pfeil 18.1 Listen
Pfeil 18.1.1 Erstes Listen-Beispiel
Pfeil 18.1.2 Auswahlkriterium ArrayList oder LinkedList
Pfeil 18.1.3 Die Schnittstelle List
Pfeil 18.1.4 ArrayList
Pfeil 18.1.5 LinkedList
Pfeil 18.1.6 Der Array-Adapter Arrays.asList(…)
Pfeil 18.1.7 ListIterator *
Pfeil 18.1.8 toArray(…) von Collection verstehen – die Gefahr einer Falle erkennen
Pfeil 18.1.9 Primitive Elemente in Datenstrukturen verwalten
Pfeil 18.2 Mengen (Sets)
Pfeil 18.2.1 Ein erstes Mengen-Beispiel
Pfeil 18.2.2 Methoden der Schnittstelle Set
Pfeil 18.2.3 HashSet
Pfeil 18.2.4 TreeSet – die sortierte Menge
Pfeil 18.2.5 Die Schnittstellen NavigableSet und SortedSet
Pfeil 18.2.6 LinkedHashSet
Pfeil 18.3 Assoziative Speicher
Pfeil 18.3.1 Die Klassen HashMap und TreeMap
Pfeil 18.3.2 Einfügen und Abfragen des Assoziativspeichers
Pfeil 18.4 Java-Stream-API
Pfeil 18.4.1 Deklaratives Programmieren
Pfeil 18.4.2 Interne versus externe Iteration
Pfeil 18.4.3 Was ist ein Stream?
Pfeil 18.5 Einen Stream erzeugen
Pfeil 18.5.1 Parallele oder sequenzielle Streams
Pfeil 18.6 Terminale Operationen
Pfeil 18.6.1 Die Anzahl der Elemente
Pfeil 18.6.2 Und jetzt alle – forEach*(…)
Pfeil 18.6.3 Einzelne Elemente aus dem Strom holen
Pfeil 18.6.4 Existenztests mit Prädikaten
Pfeil 18.6.5 Einen Strom auf sein kleinstes bzw. größtes Element reduzieren
Pfeil 18.6.6 Einen Strom mit eigenen Funktionen reduzieren
Pfeil 18.6.7 Ergebnisse in einen Container schreiben, Teil 1: collect(…)
Pfeil 18.6.8 Ergebnisse in einen Container schreiben, Teil 2: Collector und Collectors
Pfeil 18.6.9 Ergebnisse in einen Container schreiben, Teil 3: Gruppierungen
Pfeil 18.6.10 Stream-Elemente in ein Array oder einen Iterator übertragen
Pfeil 18.7 Intermediäre Operationen
Pfeil 18.7.1 Element-Vorschau
Pfeil 18.7.2 Filtern von Elementen
Pfeil 18.7.3 Statusbehaftete intermediäre Operationen
Pfeil 18.7.4 Präfix-Operation
Pfeil 18.7.5 Abbildungen
Pfeil 18.8 Zum Weiterlesen
 

Zum Seitenanfang

18.4    Java-Stream-API Zur vorigen ÜberschriftZur nächsten Überschrift

Zusammen mit Lambda-Ausdrücken, die auf der Sprache-Seite stehen, wurde in Java 8 eine ganz neue Bibliothek implementiert, die ein einfaches Verarbeiten von Datenmengen möglich macht. Ihr Name: Stream-API. In ihrem Zentrum stehen Operationen zum Filtern, Abbilden und Reduzieren von Daten aus Sammlungen.

 

Zum Seitenanfang

18.4.1    Deklaratives Programmieren Zur vorigen ÜberschriftZur nächsten Überschrift

Die Stream-API wird im funktionalen Stil genutzt, und Programme lesen sich damit sehr kompakt – die einzelnen Methoden werden in diesem Kapitel alle detailliert vorgestellt:

Object[] words = { " ", '3', null, "2", 1, "" };

Arrays.stream( words ) // Erzeugt neuen Stream

.filter( Objects::nonNull ) // Belasse Nicht-null-Referenzen im Stream

.map( Objects::toString ) // Konvertiere Objects in Strings

.map( String::trim ) // Schneide Weißraum ab

.filter( s -> ! s.isEmpty() ) // Belasse nichtleere Elemente im Stream

.map( Integer::parseInt ) // Konvertiere Strings in Ganzzahlen

.sorted() // Sortiere die Ganzzahlen

.forEach( System.out::println ); // 1 2 3

Während die Klassen aus der Collection-API optimale Speicherformen für Daten realisieren, ist es Aufgabe der Stream-API, die Daten komfortabel zu erfragen und zu aggregieren. Gut ist hier zu erkennen, dass die Stream-API das Was betont, nicht das Wie. Das heißt, Durchläufe und Iterationen kommen im Code nicht vor, sondern die Fluent-API beschreibt deklarativ, wie das Ergebnis aussehen soll. Die Bibliothek realisiert schlussendlich das Wie. So kann eine Implementierung zum Beispiel entscheiden, ob die Abarbeitung sequenziell oder parallel erfolgt, ob die Reihenfolge eine Rolle spielen muss oder ob alle Daten zwecks Sortierung zwischengespeichert werden müssen usw.

Das Pipeline-Prinzip bei Streams aus dem vorangehenden Beispiel

Abbildung 18.3     Das Pipeline-Prinzip bei Streams aus dem vorangehenden Beispiel

 

Zum Seitenanfang

18.4.2    Interne versus externe Iteration Zur vorigen ÜberschriftZur nächsten Überschrift

Als Erstes fällt bei der Stream-API auf, dass die klassische Schleife fehlt. Normalerweise gibt es Schleifen, die durch Daten laufen und dann Abfragen auf den Elementen vornehmen. Traditionelle Schleifen sind immer sequenziell und laufen von Element zu Element, und zwar vom Anfang bis zum Ende. Das Gleiche gilt auch für einen Iterator. Die Stream-API verfolgt einen anderen Ansatz. Mit ihrer Hilfe kann die externe Iteration (durch Schleifen vom Entwickler gesteuert) durch eine interne Iteration (die Stream-API holt sich Daten) abgelöst werden. Wenn etwa forEach(…) nach Daten fragt, wird die Datenquelle abgezapft und ausgesaugt, aber erst dann. Der Vorteil ist, dass wir zwar bestimmen, welche Datenstruktur abgelaufen werden soll, aber wie das intern geschieht, kann die Implementierung selbst bestimmen und dahingehend optimieren. Wenn wir selbst die Schleife schreiben, läuft die Verarbeitung immer Element für Element, während die interne Iteration auch von sich aus parallelisieren und Teilprobleme von mehreren Ausführungseinheiten berechnen lassen kann.

[»]  Hinweis

An verschiedenen Sammlungen hängt eine forEach(…)-Methode, die über alle Elemente läuft und eine Methode auf einem übergebenen Konsumenten aufruft. Das heißt jetzt nicht, dass die klassische for-Schleife – etwa über das erweiterte for – damit überflüssig wird. Neben der einfachen Schreibweise und dem einfachen Debuggen hat die übliche Schleife immer noch einige Vorteile. forEach(…) bekommt den auszuführenden Code in der Regel über einen Lambda-Ausdruck, und der hat Einschränkungen. So darf er etwa keine lokalen Variablen beschreiben (alle vom Lambda-Ausdruck adressierten lokalen Variablen sind effektiv final), und Lambda-Ausdrücke dürfen keine geprüften Ausnahmen auslösen – im Inneren einer Schleife ist das alles kein Thema. Im Übrigen gibt es für Schleifenabbrüche das break, das in Lambda-Ausdrücken nicht existiert (ein return im Lambda entspricht continue).

 

Zum Seitenanfang

18.4.3    Was ist ein Stream? Zur vorigen ÜberschriftZur nächsten Überschrift

Ein Strom ist eine Sequenz von Daten (aber keine Datenquelle an sich), die Daten wie eine Datenstruktur speichert. Die Daten vom Strom werden in einer Kette von nachgeschalteten Verarbeitungsschritten

  • gefiltert (engl. filter),

  • transformiert/abgebildet (engl. map) und

  • komprimiert/reduziert (engl. reduce).

Die Verarbeitung entlang einer Kette nennt sich Pipeline und besteht aus drei Komponenten:

  • Am Anfang steht eine Datenquelle, wie etwa ein Array, eine Datenstruktur oder ein Generator.

  • Es folgen diverse Verarbeitungsschritte wie Filterungen (Elemente verschwinden aus dem Strom) oder Abbildungen (ein Datentyp kann auch in einen anderen Datentyp konvertiert werden). Diese Veränderungen auf dem Weg nennen sich intermediäre Operationen (engl. intermediate operations). Ergebnis einer intermediären Operation ist wieder ein Stream.

  • Am Schluss wird das Ergebnis eingesammelt, und das Ergebnis ist kein Stream mehr. Eine Reduktion wäre zum Beispiel die Bildung eines Maximums oder die Konkatenation von Strings.

Die eigentliche Datenstruktur wird nicht verändert, vielmehr steht am Ende der intermediären Operationen eine terminale Operation, die das Ergebnis erfragt. So eine terminale Operation ist etwa forEach(…): Sie steht am Ende der Kette, und der Strom bricht ab.

Viele terminale Operationen reduzieren die durchlaufenden Daten auf einen Wert, anders als etwa forEach(…). Dazu gehören etwa Methoden zum einfachen Zählen der Elemente oder zum Summieren. Das nennen wir reduzierende Operationen. In der API gibt es für Standardreduktionen – wie für die Bildung der Summe, des Maximums, des Durchschnitts – vorgefertigte Methoden, doch sind allgemeine Reduktionen über eigene Funktionen möglich, etwa statt der Summe das Produkt.

Lazy Love

Alle intermediären Operationen sind »faul« (engl. lazy), weil sie die Berechnungen so lange hinausschieben, bis sie benötigt werden. Am ersten Beispiel ist das gut abzulesen: Wenn die Elemente dem Array entnommen werden, werden sie der Reihe nach zum nächsten Verarbeitungsschritt weitergereicht. Entfernt der Filter Elemente aus dem Strom, sind sie weg und müssen in einem späteren Schritt nicht mehr berücksichtigt werden. Es ist also nicht so, dass die Daten mehrfach existieren, zum Beispiel in einer Datenstruktur mit allen Elementen ohne null, dann alle Objekte, die in Strings konvertiert werden, dann alle getrimmten Strings usw.

Im Gegensatz zu den fortführenden Operationen stehen terminale Operationen, bei denen das Ergebnis vorliegen muss: Sie sind »begierig« (engl. eager). Im Prinzip wird alles so lange aufgeschoben, bis ein Wert gebraucht wird, das heißt, bis eine terminale Operation auf das Ergebnis wirklich zugreifen möchte.

Zustand ja oder nein

Intermediäre Operationen können einen Zustand haben oder nicht. Eine Filteroperation zum Beispiel hat keinen Zustand, weil sie zur Erfüllung ihrer Aufgabe nur das aktuelle Element betrachten muss, nicht aber die vorangehenden. Eine Sortierungsoperation hat dagegen einen Status: Sie »will«, dass alle anderen Elemente gespeichert werden, denn das aktuelle Element reicht für die Sortierung nicht aus, sondern auch alle vorangehenden werden benötigt.

 


Ihre Meinung?

Wie hat Ihnen das Openbook gefallen? Wir freuen uns immer über Ihre Rückmeldung. Schreiben Sie uns gerne Ihr Feedback als E-Mail an kommunikation@rheinwerk-verlag.de

<< zurück
 Zum Rheinwerk-Shop
Zum Rheinwerk-Shop: Java ist auch eine Insel Java ist auch eine Insel

Jetzt Buch bestellen


 Buchempfehlungen
Zum Rheinwerk-Shop: Captain CiaoCiao erobert Java

Captain CiaoCiao erobert Java




Zum Rheinwerk-Shop: Algorithmen in Java

Algorithmen in Java




Zum Rheinwerk-Shop: Spring Boot 3 und Spring Framework 6

Spring Boot 3 und Spring Framework 6




Zum Rheinwerk-Shop: Java SE 9 Standard-Bibliothek

Java SE 9 Standard-Bibliothek




 Lieferung
Versandkostenfrei bestellen in Deutschland, Österreich und in die Schweiz

InfoInfo



 

 


Copyright © Rheinwerk Verlag GmbH 2024

Für Ihren privaten Gebrauch dürfen Sie die Online-Version natürlich ausdrucken. Ansonsten unterliegt das Openbook denselben Bestimmungen, wie die gebundene Ausgabe: Das Werk einschließlich aller seiner Teile ist urheberrechtlich geschützt.

Alle Rechte vorbehalten einschließlich der Vervielfältigung, Übersetzung, Mikroverfilmung sowie Einspeicherung und Verarbeitung in elektronischen Systemen.

 

[Rheinwerk Computing]



Rheinwerk Verlag GmbH, Rheinwerkallee 4, 53227 Bonn, Tel.: 0228.42150.0, Fax 0228.42150.77, service@rheinwerk-verlag.de



Cookie-Einstellungen ändern