Rheinwerk Computing < openbook >


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


Download:

- Listings, ca. 2,7 MB


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 17 Einführung in Datenstrukturen und Algorithmen
Pfeil 17.1 Listen
Pfeil 17.1.1 Erstes Listen-Beispiel
Pfeil 17.1.2 Auswahlkriterium ArrayList oder LinkedList
Pfeil 17.1.3 Die Schnittstelle List
Pfeil 17.1.4 ArrayList
Pfeil 17.1.5 LinkedList
Pfeil 17.1.6 Der Array-Adapter Arrays.asList(…)
Pfeil 17.1.7 ListIterator *
Pfeil 17.1.8 toArray(…) von Collection verstehen – die Gefahr einer Falle erkennen
Pfeil 17.1.9 Primitive Elemente in Datenstrukturen verwalten
Pfeil 17.2 Mengen (Sets)
Pfeil 17.2.1 Ein erstes Mengen-Beispiel
Pfeil 17.2.2 Methoden der Schnittstelle Set
Pfeil 17.2.3 HashSet
Pfeil 17.2.4 TreeSet – die sortierte Menge
Pfeil 17.2.5 Die Schnittstellen NavigableSet und SortedSet
Pfeil 17.2.6 LinkedHashSet
Pfeil 17.3 Java Stream-API
Pfeil 17.3.1 Deklaratives Programmieren
Pfeil 17.3.2 Interne versus externe Iteration
Pfeil 17.3.3 Was ist ein Stream?
Pfeil 17.4 Einen Stream erzeugen
Pfeil 17.4.1 Parallele oder sequenzielle Streams
Pfeil 17.5 Terminale Operationen
Pfeil 17.5.1 Die Anzahl der Elemente
Pfeil 17.5.2 Und jetzt alle – forEachXXX(…)
Pfeil 17.5.3 Einzelne Elemente aus dem Strom holen
Pfeil 17.5.4 Existenz-Tests mit Prädikaten
Pfeil 17.5.5 Einen Strom auf sein kleinstes bzw. größtes Element reduzieren
Pfeil 17.5.6 Einen Strom mit eigenen Funktionen reduzieren
Pfeil 17.5.7 Ergebnisse in einen Container schreiben, Teil 1: collect(…)
Pfeil 17.5.8 Ergebnisse in einen Container schreiben, Teil 2: Collector und Collectors
Pfeil 17.5.9 Ergebnisse in einen Container schreiben, Teil 3: Gruppierungen
Pfeil 17.5.10 Stream-Elemente in ein Array oder einen Iterator übertragen
Pfeil 17.6 Intermediäre Operationen
Pfeil 17.6.1 Element-Vorschau
Pfeil 17.6.2 Filtern von Elementen
Pfeil 17.6.3 Statusbehaftete intermediäre Operationen
Pfeil 17.6.4 Präfix-Operation
Pfeil 17.6.5 Abbildungen
Pfeil 17.7 Zum Weiterlesen
 

Zum Seitenanfang

17.3    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

17.3.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 17.2    Das Pipeline-Prinzip bei Streams aus dem vorangehenden Beispiel

 

Zum Seitenanfang

17.3.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

17.3.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 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 Maxiums 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 aus 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 Srings 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: Java SE 9 Standard-Bibliothek

Java SE 9 Standard-Bibliothek




Zum Rheinwerk-Shop: Algorithmen in Java

Algorithmen in Java




Zum Rheinwerk-Shop: Objektorientierte Programmierung

Objektorientierte Programmierung




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

InfoInfo



 

 


Copyright © Rheinwerk Verlag GmbH 2021

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