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

Pfeil17 Einführung in Datenstrukturen und Algorithmen
Pfeil17.1 Listen
Pfeil17.1.1 Erstes Listen-Beispiel
Pfeil17.1.2 Auswahlkriterium ArrayList oder LinkedList
Pfeil17.1.3 Die Schnittstelle List
Pfeil17.1.4 ArrayList
Pfeil17.1.5 LinkedList
Pfeil17.1.6 Der Array-Adapter Arrays.asList(…)
Pfeil17.1.7 ListIterator *
Pfeil17.1.8 toArray(…) von Collection verstehen – die Gefahr einer Falle erkennen
Pfeil17.1.9 Primitive Elemente in Datenstrukturen verwalten
Pfeil17.2 Mengen (Sets)
Pfeil17.2.1 Ein erstes Mengen-Beispiel
Pfeil17.2.2 Methoden der Schnittstelle Set
Pfeil17.2.3 HashSet
Pfeil17.2.4 TreeSet – die sortierte Menge
Pfeil17.2.5 Die Schnittstellen NavigableSet und SortedSet
Pfeil17.2.6 LinkedHashSet
Pfeil17.3 Java Stream-API
Pfeil17.3.1 Deklaratives Programmieren
Pfeil17.3.2 Interne versus externe Iteration
Pfeil17.3.3 Was ist ein Stream?
Pfeil17.4 Einen Stream erzeugen
Pfeil17.4.1 Parallele oder sequenzielle Streams
Pfeil17.5 Terminale Operationen
Pfeil17.5.1 Die Anzahl der Elemente
Pfeil17.5.2 Und jetzt alle – forEachXXX(…)
Pfeil17.5.3 Einzelne Elemente aus dem Strom holen
Pfeil17.5.4 Existenz-Tests mit Prädikaten
Pfeil17.5.5 Einen Strom auf sein kleinstes bzw. größtes Element reduzieren
Pfeil17.5.6 Einen Strom mit eigenen Funktionen reduzieren
Pfeil17.5.7 Ergebnisse in einen Container schreiben, Teil 1: collect(…)
Pfeil17.5.8 Ergebnisse in einen Container schreiben, Teil 2: Collector und Collectors
Pfeil17.5.9 Ergebnisse in einen Container schreiben, Teil 3: Gruppierungen
Pfeil17.5.10 Stream-Elemente in ein Array oder einen Iterator übertragen
Pfeil17.6 Intermediäre Operationen
Pfeil17.6.1 Element-Vorschau
Pfeil17.6.2 Filtern von Elementen
Pfeil17.6.3 Statusbehaftete intermediäre Operationen
Pfeil17.6.4 Präfix-Operation
Pfeil17.6.5 Abbildungen
Pfeil17.7 Zum Weiterlesen
 

Zum Seitenanfang

17.6    Intermediäre Operationen Zur vorigen ÜberschriftZur nächsten Überschrift

Alle terminalen Operationen führen zu keinem neuen veränderten Stream, sondern beenden den aktuellen Stream. Anders sieht das bei intermediären Operationen aus: Sie verändern den Stream, indem sie etwa Elemente herausnehmen, auf andere Werte und Typen abbilden oder sortieren. Jede der intermediären Methoden liefert ein neues Stream-Objekt. Das haben wir in den ersten Schreibweisen durch die Konkatenation etwas verschleiert, aber vollständig sieht es so aus:

Vollständige Schreibweise

Kaskadierte Schreibweise

Stream<Object> a =

Stream.of(" ",'3',null,"2",1,"");

Stream<Object> b = a.filter(Objects::nonNull);

Stream<String> c = b.map(Objects::toString );

Stream<String> d = c.map(String::trim);

Stream<String> e = d.filter(s -> !s.isEmpty());

Stream<Integer> f = e.map(Integer::parseInt);

Stream<Integer> g = f.sorted();

g.forEach(System.out::println);
Stream

.of(" ",'3',null,"2",1,"")

.filter(Objects::nonNull)

.map(Objects::toString)

.map(String::trim)

.filter(s -> ! s.isEmpty())

.map(Integer::parseInt)

.sorted()

.forEach(System.out::println);

Tabelle 17.4    Ausführliche und kompakte Schreibweise im Vergleich

[»]  Hinweis

Keine Strommethode darf die Stromquelle modifizieren, da das Ergebnis sonst unbestimmt ist. Veränderungen treten nur entlang der Kette von einem Strom zum nächsten auf. Wir haben das schon in Abschnitt 17.5.8, »Ergebnisse in einen Container schreiben, Teil 2: Collector und Collectors«, diskutiert.

 

Zum Seitenanfang

17.6.1    Element-Vorschau Zur vorigen ÜberschriftZur nächsten Überschrift

Eine einfache intermediäre Operation ist peek(…). Sie darf sich das aktuelle Element während des Durchlaufs anschauen.

interface java.util.stream.Stream<T>

extends BaseStream<T,Stream<T>>
  • Stream<T> peek(Consumer<? super T> action)

[zB]  Beispiel

Schaue in den Strom vor und nach einer Sortierung:

System.out.println( Stream.of( 9, 4, 3 )

.peek( System.out::println ) // 9 4 3

.sorted()

.peek( System.out::println ) // 3 4 9

.collect( Collectors.toList() ) );
 

Zum Seitenanfang

17.6.2    Filtern von Elementen Zur vorigen ÜberschriftZur nächsten Überschrift

Eine der wichtigsten Stream-Methoden ist filter(…): Sie liefert alle Elemente im Stream, die einem Kriterium genügen, und ignoriert alle anderen, die nicht diesem Kriterium genügen.

interface java.util.stream.Stream<T>

extends BaseStream<T,Stream<T>>
  • Stream<T> filter(Predicate<? super T> predicate)

[zB]  Beispiel

Gib alle Wörter aus, bei denen zwei beliebige Vokale hintereinander vorkommen:

Stream.of( "Moor", "Aha", "Meister" )

.filter( Pattern.compile( "[aeiou]{2}" ).asPredicate() )

.forEach( System.out::println ); // Moor Meister
 

Zum Seitenanfang

17.6.3    Statusbehaftete intermediäre Operationen Zur vorigen ÜberschriftZur nächsten Überschrift

Die allermeisten intermediären Operationen können Elemente direkt während des Durchlaufs bewerten und verändern, sodass keine speicherintensive Zwischenspeicherung nötig ist. Einige intermediäre Operationen haben jedoch einen Status. Dazu zählen:

  • limit(long): begrenzt den Strom auf eine gewisse Anzahl maximaler Elemente.

  • skip(long): überspringt eine Anzahl Elemente.

  • distinct(): löscht alle doppelten Elemente.

  • sorted(…): sortiert den Strom.

interface java.util.stream.Stream<T>

extends BaseStream<T,Stream<T>>
  • Stream<T> limit(long maxSize)

  • Stream<T> skip(long n)

  • Stream<T> distinct()

  • Stream<T> sorted()

  • Stream<T> sorted(Comparator<? super T> comparator)

[zB]  Beispiel 1

Was ist das längste Wort der Liste?

String longest = Stream.of( "Autos", "können", "nicht", "versaut", "genug", "sein" )

.sorted( Comparator.comparing( String::length ).reversed() )

.findFirst().get();

System.out.println( longest ); // versaut

Alternativ mit max(…):

String longest = …

.max( Comparator.comparing( String::length ) )

.get();
[zB]  Beispiel 2

Zerlege eine Zeichenkette in Teilzeichenketten, bilde diese auf das ersten Zeichen ab, und lösche aus dem Stream doppelte Elemente:

List<String> list = Pattern.compile( " " ).splitAsStream( "Pu Po Aha La" )

.map( s -> s.substring(0, 1) )

.peek( System.out::println ) // P P A L

.distinct() // \/\/\/

.peek( System.out::println ) // P A L

.collect( Collectors.toList() );

System.out.print( list ); // [P, A, L]

peek(…) macht gut deutlich, wie die Elemente vor und nach dem Anwenden von distinct() aussehen.

[»]  Hinweis

Eine Methode wie skip(…) sieht auf den ersten Blick unschuldig aus, kann aber ganz schön auf den Speicherbedarf und die Performance gehen, wenn parallele Ströme mit Ordnung (etwa durch Sortierung) ins Spiel kommen. Bei einem stream.parallel().sorted().skip(10000) müssen trotzdem alle Elemente erst sortiert werden, damit die ersten 10.000 übersprungen werden können. Sequenzielle Ströme bzw. Ströme ohne Ordnung (die liefert die Stream-Methode unordered()) sind ungleich schneller, aber natürlich nicht in jedem Fall möglich.

 

Zum Seitenanfang

17.6.4    Präfix-Operation Zur vorigen ÜberschriftZur nächsten Überschrift

Unter einem Präfix verstehen wir eine Teilfolge eines Streams, die beim ersten Element beginnt. Wir können mit limit(long) selbst ein Präfix generieren, doch im Allgemeinen geht es darum, eine Bedingung zu haben, die alle Elemente eines Präfix-Streams erfüllen müssen, und den Stream zu beenden, wenn die Bedingung für ein Element nicht mehr gilt. Java 9 deklariert dafür zwei neue Methoden, takeWhile(…) und dropWhile(…):

  • default Stream<T> takeWhile(Predicate<? super T> predicate)

  • default Stream<T> dropWhile(Predicate<? super T> predicate)

Die deutsche Übersetzung von takeWhile(…) wäre »nimm, solange predicate gilt« und dropWhile(…) »lass fallen, solange predicate gilt«.

[zB]  Beispiel

Der Stream soll beim Eintreffen des Wortes »Trump« sofort enden:

new Scanner( "Dann twitterte Trump am 7. Nov. 2012: "

+ "'It's freezing and snowing in New York--we need global warming!'" )

.useDelimiter( "\\P{Alpha}+" ).tokens()

.takeWhile( s -> !s.equalsIgnoreCase( "trump" ) )

.forEach( System.out::println ); // Dann twitterte

}

Der Stream soll nach dem längsten Präfix beginnen, und dann enden, wenn eine Zahl kleiner gleich 0 ist:

Stream.of( 1, 2, -1, 3, 4, -1, 5, 6 )

.dropWhile( i -> i > 0 ) // !(i>0), also i<=0 wird übersprungen

.forEach( System.out::println ); // -1 3 4 -1 5 6

Das Element, das das Prädikat erfüllt, ist selbst das erste Element im neuen Stream. Wir können es mit skip(1) überspringen.

Erfüllt schon bei takeWhile(…) das erste Element nicht das Prädikat, so ist der Stream leer. takeWhile(…) und dropWhile(…) können zusammen verwendet werden: So liefert Stream.of( 1, 2, -1, 3, 4, -1, 5, 6 ).dropWhile( i -> i > 0 ).skip( 1 ).takeWhile( i -> i > 0 ).forEach( System.out::println ); die Ausgaben 3 4.

[»]  Hinweis

Präfixe sind nur für geordnete Streams sinnvoll. Und wenn Streams parallel sind, müssen sie für die Präfixberechnung wieder in die richtige Reihenfolge gebracht werden. Das ist eine eher teure Operation.

 

Zum Seitenanfang

17.6.5    Abbildungen Zur vorigen ÜberschriftZur nächsten Überschrift

Die Fähigkeit, Elemente im Strom auf neue Elemente abzubilden ist eines der mächtigsten Werkzeuge der Stream-API. Zu unterscheiden sind drei Typen von Abbildungsmethoden:

  • Die einfachste Variante ist map(Function). Die Funktion bekommt das Stromelement als Argument und liefert ein Ergebnis, das dann in den Strom kommt. Hierbei kann sich der Typ ändern, wenn die Funktion nicht den gleichen Typ gibt, wie sie nimmt. Die Funktion wird nach und nach auf jedem Element angewendet, und die Reihenfolge ergibt sich aus der Ordnung des Stroms.

  • Die drei Methoden mapTo[Int|Long|Double]([int|long|double]Function) arbeiten wie map(Function), nur liefern die Funktionen einen primitiven Wert, und das Ergebnis ist ein primitiver Stream.

  • flatMapXXX(XXXFunction)-Methoden ersetzen ebenfalls Elemente im Stream, mit dem Unterschied, dass die Funktionen alle selbst Stream-Objekte liefern, deren Elemente dann in den Ergebnisstrom gesetzt werden. Der Begriff flat (engl. für »flach«) kommt daher, dass die »inneren« Streams nicht selbst alle als Elemente in den Ursprungs-Stream kommen (sozusagen ein Stream<Stream>), sondern »flachgeklopft«werden. Anwendungsbeispiele sind in der Regel Szenarien wie »n Spieler assoziieren m Gegenstände, gesucht ist ein Strom aller Gegenstände aller Spieler«. Die Funktion kann null liefern, wenn nichts in den Stream gelegt werden soll.

interface java.util.stream.Stream<T>

extends BaseStream<T,Stream<T>>
  • <R> Stream<R> map(Function<? super T,? extends R> mapper)

  • IntStream mapToInt(ToIntFunction<? super T> mapper)

  • LongStream mapToLong(ToLongFunction<? super T> mapper)

  • DoubleStream mapToDouble(ToDoubleFunction<? super T> mapper)

  • <R> Stream<R> flatMap(Function<? super T,? extends Stream<? extends R>> mapper)

  • IntStream flatMapToInt(Function<? super T,? extends IntStream> mapper)

  • LongStream flatMapToLong(Function<? super T,? extends LongStream> mapper)

  • DoubleStream flatMapToDouble(Function<? super T,? extends DoubleStream> mapper)

[zB]  Beispiel

Konvertiere ein String-Array mit Zahlen in ein long-Array:

String[] numbers = { "1", "2", "3" };

long[] parseInts = Stream.of( numbers ).mapToLong( Long::parseLong ).toArray();
[zB]  Beispiel

Die Methode Locale.getAvailableLocales() liefert ein Array von Locale-Objekten, die ein System unterstützt. Wir interessieren uns für alle Ländercodes:

Stream.of( Locale.getAvailableLocales() )

.map( Locale::getCountry )

.distinct()

.forEach( System.out::println );

Über ein Locale-Objekt erfragt DateFormatSymbols.getInstance(locale).getWeekdays() die Namen der Wochentage. Wir interessieren uns als Ergebnis für alle Wochentage aller installierten Gebiete:

Stream.of( Locale.getAvailableLocales() )

.flatMap( l -> Stream.of( DateFormatSymbols.getInstance( l ).getWeekdays() ) )

.filter( s -> ! s.isEmpty() )

.distinct()

.forEach( System.out::println );
[+]  Tipp

Die an flatMap(…) übergebene Funktion muss als Ergebnis einen Stream liefern oder null, wenn nichts passieren soll. Es ist nicht verkehrt, immer einen Stream zu liefern und statt null der Funktion einen leeren Stream mit Stream.empty() zurückgeben zu lassen. Anwendungen für diese leeren Ströme kommen aus folgendem Szenario: In der API gibt es Methoden, die statt Arrays bzw. Sammlungen nur null zurückgeben. Nehmen wir an, result ist so eine Rückgabe, die null oder ein Array sein kann, dann bekommen wir einen Stream wie folgt:

Stream<…> flatStream = Optional.ofNullable( result )

.map( Stream::of ).orElse( Stream.empty() );

Die Fallunterscheidung, ob die Sammlung null ist, ist hier funktional mit Optional gelöst, alternativ mit result != null ? result : Stream.empty(). Das statische Stream.of(…), das wir hier über eine Methodenreferenz nutzen, funktioniert für ein Array; für einen Stream aus einer Sammlung wäre result.stream() nötig. Fassen wir das in einem Beispiel zusammen. Die Class-Methode getEnumConstants()liefert ein Array von Konstanten, wenn das Class-Objekt eine Aufzählung repräsentiert – andernfalls ist das Array nicht etwa leer, sondern null. (So lässt sich unterscheiden, ob das Class-Objekt entweder keine Elemente hat oder überhaupt kein Aufzählungstyp ist.) Wir wollen von drei Class-Objekten alle Konstanten einsammeln und ausgeben:

Stream.of( Object.class, Thread.State.class, DayOfWeek.class )

.flatMap( clazz -> Optional.ofNullable( clazz.getEnumConstants() )

.map( Stream::of ).orElse( Stream.empty() ) )

.forEach( System.out::println );

null ist oft ein ungebetener Gast, und die Stream-Methode ofNullable(…) in Java 9 hilft, Fehler zu vermeiden. Nehmen wir Folgendes, um alle Koordinaten in einen Stream zu bekommen:

Stream.of( null, new Point( 1, 2 ), null, new Point( 3, 4 ) )

.flatMap( q -> Stream.of( q.x, q.y ) )

.forEach( System.out::println );

Bei flatMap(…) wird eine NullPointerException folgen. Mit filter(…) könnten wir im Vorfeld jede null ausblenden, doch eine andere Lösung wäre:

Stream.of( null, new Point( 1, 2 ), null, new Point( 3, 4 ) )

.flatMap( p -> Stream.ofNullable( p ).flatMap( q -> Stream.of( q.x, q.y ) ) )

.forEach( System.out::println );

 


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