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

Pfeil12 Lambda-Ausdrücke und funktionale Programmierung
Pfeil12.1 Funktionale Schnittstellen und Lambda-Ausdrücke
Pfeil12.1.1 Klassen implementieren Schnittstellen
Pfeil12.1.2 Lambda-Ausdrücke implementieren Schnittstellen
Pfeil12.1.3 Funktionale Schnittstellen
Pfeil12.1.4 Der Typ eines Lambda-Ausdrucks ergibt sich durch den Zieltyp
Pfeil12.1.5 Annotation @FunctionalInterface
Pfeil12.1.6 Syntax für Lambda-Ausdrücke
Pfeil12.1.7 Die Umgebung der Lambda-Ausdrücke und Variablenzugriffe
Pfeil12.1.8 Ausnahmen in Lambda-Ausdrücken
Pfeil12.1.9 Klassen mit einer abstrakten Methode als funktionale Schnittstelle? *
Pfeil12.2 Methodenreferenz
Pfeil12.2.1 Motivation
Pfeil12.2.2 Methodenreferenzen mit ::
Pfeil12.2.3 Varianten von Methodenreferenzen
Pfeil12.3 Konstruktorreferenz
Pfeil12.3.1 Parameterlose und parametrisierte Konstruktoren
Pfeil12.3.2 Nützliche vordefinierte Schnittstellen für Konstruktorreferenzen
Pfeil12.4 Funktionale Programmierung
Pfeil12.4.1 Code = Daten
Pfeil12.4.2 Programmierparadigmen: imperativ oder deklarativ
Pfeil12.4.3 Das Wesen der funktionalen Programmierung
Pfeil12.4.4 Funktionale Programmierung und funktionale Programmiersprachen
Pfeil12.4.5 Funktionen höherer Ordnung am Beispiel von Comparator
Pfeil12.4.6 Lambda-Ausdrücke als Abbildungen bzw. Funktionen betrachten
Pfeil12.5 Funktionale Schnittstellen aus dem java.util.function-Paket
Pfeil12.5.1 Blöcke mit Code und die funktionale Schnittstelle Consumer
Pfeil12.5.2 Supplier
Pfeil12.5.3 Prädikate und java.util.function.Predicate
Pfeil12.5.4 Funktionen über die funktionale Schnittstelle java.util.function.Function
Pfeil12.5.5 Ein bisschen Bi …
Pfeil12.5.6 Funktionale Schnittstellen mit Primitiven
Pfeil12.6 Optional ist keine Nullnummer
Pfeil12.6.1 Einsatz von null
Pfeil12.6.2 Der Optional-Typ
Pfeil12.6.3 Primitive optionale Typen
Pfeil12.6.4 Erst mal funktional mit Optional
Pfeil12.6.5 Primitiv-Optionales mit speziellen OptionalXXX-Klassen
Pfeil12.7 Was ist jetzt so funktional?
Pfeil12.8 Zum Weiterlesen
 

Zum Seitenanfang

12.5    Funktionale Schnittstellen aus dem java.util.function-Paket Zur vorigen ÜberschriftZur nächsten Überschrift

Funktionen realisieren Abbildungen, und da es verschiedene Arten von Abbildungen geben kann, bietet die Java-Standardbibliothek im Paket java.util.function für die häufigsten Fälle funktionale Schnittstellen an. Tabelle 12.12 bietet einen ersten Überblick:

Schnittstelle

Abbildung

Consumer<T>

(T) → void

DoubleConsumer

(double) → void

BiConsumer<T,U>

(T, U) → void

Supplier<T>

() → T

BooleanSupplier

() → boolean

Predicate<T>

(T) → boolean

LongPredicate

(long) → boolean

BiPredicate<T,U>

(T, U) → boolean

Function<T,R>

(T) → R

LongToDoubleFunction

(long) → double

BiFunction<T,U,R>

(T, U) → R

UnaryOperator<T>

(T) → T

DoubleBinaryOperator

(double, double) → double

Tabelle 12.12    Beispiele einiger vordefinierter funktionaler Schnittstellen

 

Zum Seitenanfang

12.5.1    Blöcke mit Code und die funktionale Schnittstelle Consumer Zur vorigen ÜberschriftZur nächsten Überschrift

Anweisungen von Code lassen sich in eine Methode eines Objekts setzen und auf diese Weise weitergeben. Das ist eine häufige Notwendigkeit, für die das Paket java.util.function eine einfache funktionale Schnittstelle Consumer vorgibt, die einen Konsumenten repräsentiert, der Daten annimmt und dann »verbraucht« (konsumiert) und nichts zurückgibt.

interface java.util.function.Consumer<T>
  • void accept(T t)

    Führt Operationen mit der Übergabe t durch.

  • default Consumer<T> andThen(Consumer<? super T> after)

    Liefert einen neuen Consumer, der erst den aktuellen Consumer ausführt und danach after.

Die accept(…)-Methode bekommt ein Argument – wobei die Implementierung natürlich nicht zwingend darauf zurückgreifen muss – und liefert keine Rückgabe. Transformationen sind damit nicht möglich, denn nur über Umwege kann der Konsument die Ergebnisse speichern, und dafür ist die Schnittstelle nicht gedacht. Consumer-Typen sind eher als Endglied einer Kette gedacht, in der zum Beispiel Daten in eine Datei geschrieben werden, die vorher verarbeitet wurden. Diese Seiteneffekte sind beabsichtigt, da sie nach einer Kette von seiteneffektfreien Operationen stehen.

Typ Consumer in der API

In der Java-API zeigt sich der Typ Consumer in der Regel als Argument einer Methode forEach(Consumer), die Datenquellen abläuft und für jedes Element accept(…) aufruft. Interessant ist die Methode am Typ Iterable, denn die wichtigen Collection-Datenstrukturen wie ArrayList implementieren diese Schnittstelle. So lässt sich einfach über alle Daten laufen und ein Stück Code für jedes Element ausführen. Auch Iterator hat eine vergleichbare Methode. Da heißt sie forEachRemaining(Consumer) – das »Remaining« macht deutlich, dass der Iterator schon ein paar next()-Aufrufe erlebt haben könnte und die Konsumenten daher nicht zwingend die ersten Elemente mitbekommen.

[zB]  Beispiel

Gib jedes Element einer Liste auf der Konsole aus:

Arrays.asList( 1, 2, 3, 4 ).forEach( System.out::println );

Gegenüber einem normalen Durchiterieren ist die funktionale Variante ein wenig kürzer im Code, aber sonst gibt es keinen Unterschied. Auch forEach(…) macht auf dem Iterable nichts anderes, als alle Elemente über den Iterator zu holen.

Einen eigenen Wrapper-Consumer schreiben

Immer repräsentieren Konsumenten Code, und eine API kann nun einfach einen Codeblock nach der Art doSomethingWith(myConsumer) annehmen, um ihn etwa in einem Hintergrund-Thread abzuarbeiten oder wiederholend auszuführen. Sie kann ihn auch nach einer erlaubten Maximaldauer abbrechen oder die Zeit messen oder, oder, oder …

Schreiben wir einen Consumer-Wrapper, der die Ausführungszeit eines anderen Konsumenten loggt:

Listing 12.11    src/main/java/com/tutego/insel/lambda/Consumers.java, Ausschnitt

class Consumers {

public static <T> Consumer<T> measuringConsumer( Consumer<T> block ) {

return t -> {

long start = System.nanoTime();

block.accept( t );

long duration = System.nanoTime() - start;

Logger.getAnonymousLogger().info( "Ausführungszeit (ns): " + duration );

};

}

}

Folgender Aufruf zeigt die Nutzung:

Arrays.asList( 1, 2, 3, 4 )

.forEach( executionTimeLogger( System.out::println ) );

Was wir hier implementiert haben, ist ein Beispiel für das Execute-around-Method-Muster, bei dem wir um einen Block Code noch etwas anderes legen.

 

Zum Seitenanfang

12.5.2    Supplier Zur vorigen ÜberschriftZur nächsten Überschrift

Ein Supplier (auch Provider genannt) ist eine Fabrik und sorgt für Objekte. In Java deklariert das Paket java.util.function die funktionale Schnittstelle Supplier für Objektgeber:

interface java.util.function.Supplier<T>
  • T get()

    Führt Operationen mit der Übergabe t durch.

Weitere statische oder Default-Methoden deklariert Supplier nicht. Was get() nun genau liefert, ist Aufgabe der Implementierung und ein Internum. Es können neue Objekte sein, immer die gleichen Objekte (Singleton) oder Objekte aus einem Cache.

 

Zum Seitenanfang

12.5.3    Prädikate und java.util.function.Predicate Zur vorigen ÜberschriftZur nächsten Überschrift

Ein Prädikat ist eine Aussage über einen Gegenstand, die wahr oder falsch ist. Die Frage "tutego".isEmpty(), ob also die Zeichenfolge »tutego« leer ist oder nicht, wird mit falsch beantwortet – isEmpty ist also ein Prädikat, weil es über einen Gegenstand (eine Zeichenfolge in unserem Fall) eine Wahrheitsaussage fällen kann.

Prädikate als Objekte sind flexibel, denn Objekte lassen sich an unterschiedliche Stellen weitergeben. So kann ein Prädikat bestimmen, was aus einer Sammlung gelöscht werden soll oder ob mindestens ein Element in einer Sammlung ist.

Das java.util.function-Paket[ 225 ](Achtung, in javax.sql.rowset gibt es ebenfalls eine Schnittstelle Predicate. ) deklariert eine flexible funktionale Schnittstelle Predicate auf folgende Weise:

interface java.util.function.Predicate<T>
  • boolean test(T t)

    Führt einen Test auf t durch und liefert true, wenn das Kriterium erfüllt ist, sonst false.

[zB]  Beispiel

Der Test, ob ein Zeichen eine Ziffer ist, kann durch Prädikat-Objekte nun auch anders durchgeführt werden:

Predicate<Character> isDigit = 

c -> Character.isDigit( c ); // kurz: Character::isDigit

System.out.println( isDigit.test('a') ); // false

Hätte es die Schnittstelle Predicate schon früher in Java 1.0 gegeben, hätte es der Methode Character.isDigit(…) gar nicht bedurft, es hätte auch ein Predicate<Character> als statische Variable in der Klasse Character geben können, sodass ein Test dann als Character.IS_DIGIT.test(…) geschrieben würde oder als Rückgabe von einer Methode Predicate<Character> isDigit() mit der Nutzung Character.isDigit().test(…). Es ist daher gut möglich, dass sich in Zukunft die API dahingehend verändert, dass Aussagen auf Gegenständen mit Wahrheitsrückgabe nicht mehr als Methoden bei den Klassen realisiert werden, sondern als Prädikat-Objekte angeboten werden. Aber Methodenreferenzen geben uns zum Glück die Flexibilität, dass existierende Methoden problemlos als Lambda-Ausdrücke genutzt werden können, und so kommen wir wieder von Methoden zu Funktionen.

Der Typ Predicate in der API

Es gibt in der Java-API einige Stellen, an denen Predicate-Objekte genutzt werden:

  • als Argument für Löschmethoden, um in Sammlungen Elemente zu spezifizieren, die gelöscht werden sollen oder nach denen gefiltert werden soll

  • bei den Default-Methoden der Predicate-Schnittstelle selbst, um Prädikate zu verknüpfen

  • bei regulären Ausdrücken; auf einem Pattern liefern asPredicate() und asMatchPredicate() (ab Java 11) ein Predicate für Tests.

  • in der Stream-API, bei der Objekte beim Durchlaufen des Stroms über ein Prädikat identifiziert werden, um sie etwa auszufiltern

[zB]  Beispiel

Lösche aus einer Liste mit beliebigen Zeichen alle heraus, die Ziffern sind:

Predicate<Character> isDigit = Character::isDigit;

List<Character> list = new ArrayList<>( Arrays.asList( 'a', '1' ) );

list.removeIf( isDigit );

Auf diese Weise steht nicht die Schleife, sondern das Löschen im Vordergrund.

Default-Methoden von Predicate

Es gibt eine Reihe von Default-Methoden, die die funktionale Schnittstelle Predicate anbietet:

interface java.util.function.Predicate<T>
  • default Predicate<T> negate()

    Liefert vom aktuellen Prädikat eine Negation. Implementiert als return t -> ! test(t);.

  • static <T> Predicate<T> not(Predicate<? super T> target)

    Liefert target.negate(). Neu in Java 11.

  • default Predicate<T> and(Predicate<? super T> p)

  • default Predicate<T> or(Predicate<? super T> p)

    Verknüpft das aktuelle Prädikat mit einem anderen Prädikat mit einem logischen Und/Oder.

  • static <T> Predicate<T> isEqual(Object targetRef)

    Liefert ein neues Prädikat, das einen Gleichwertigkeitstest mit targetRef vornimmt, im Grunde return ref -> Objects.equals(ref, targetRef).

[zB]  Beispiel

Lösche aus einer Liste mit Zeichen diejenigen, die keine Ziffern sind:

Predicate<Character> isDigit = Character::isDigit;

Predicate<Character> isNotDigit = isDigit.negate();

List<Character> list = new ArrayList<>( Arrays.asList( 'a', '1' ) );

list.removeIf( isNotDigit );

// alternativ: list.removeIf( Predicate.not( isDigit ) );
 

Zum Seitenanfang

12.5.4    Funktionen über die funktionale Schnittstelle java.util.function.Function Zur vorigen ÜberschriftZur nächsten Überschrift

Funktionen im Sinne der funktionalen Programmierung können in verschiedenen Bauarten vorkommen: mit Parameterliste/Rückgabe oder ohne. Doch im Grunde sind es Spezialformen, und die funktionale Schnittstelle java.util.function.Function ist die allgemeinste, die zu einem Argument ein Ergebnis liefert.

interface java.util.function.Function<T,R>
  • R apply(T t)

    Wendet eine Funktion an und liefert zur Eingabe t eine Rückgabe.

[zB]  Beispiel

Eine Funktion zur Bestimmung des Absolutwerts:

Function<Double,Double> abs = a -> Math.abs( a );  // alternativ Math::abs

System.out.println( abs.apply( -12. ) ); // 12.0

Auch bei Funktionen ergibt sich für das API-Design ein Spannungsfeld, denn im Grunde müssen »Funktionen« nun gar nicht mehr als Methoden angeboten werden, sondern Klassen könnten sie auch als Function-Objekte anbieten. Doch da Methodenreferenzen problemlos die Brücke von Methodennamen zu Objekten schlagen, fahren Entwickler mit klassischen Methoden ganz gut.

Der Typ Function in der API

Die Stream-API ist der größte Nutznießer des Function-Typs. Es finden sich einige wenige Beispiele bei Objektvergleichen (Comparator), im Paket für Nebenläufigkeiten und bei Assoziativspeichern. Im Abschnitt über die Stream-API werden wir daher viele weitere Beispiele kennenlernen.

[zB]  Beispiel

Ein Assoziativspeicher soll als Cache realisiert werden, der zu Dateinamen den Inhalt assoziiert. Ist zu dem Schlüssel (dem Dateinamen) noch kein Inhalt vorhanden, soll der Dateiinhalt gelesen und in den Assoziativspeicher gelegt werden.

Listing 12.12    src/main/java/com/tutego/insel/lambda/FileCache.java, Ausschnitt

class FileCache {

private Map<String,byte[]> map = new HashMap<>();

public byte[] getContent( String filename ) {

return map.computeIfAbsent( filename, file -> {

try {

return Files.readAllBytes( Paths.get( file ) );

} catch ( IOException e ) { throw new UncheckedIOException( e ); }

} );

}

}

Auf die Methode kommen wir in Kapitel 17, »Einführung in Datenstrukturen und Algorithmen«, noch einmal zurück, das Beispiel soll nur eine Vorstellung davon geben, dass Funktionen an andere Funktionen übergeben werden – hier eine Function<String,byte[]) an computeIfAbsent(…). Sobald an den Datei-Cache der Aufruf getContent(String) geht, wird dieser die Map fragen, und wenn diese zu dem Schlüssel keinen Wert hat, wird sie den Lambda-Ausdruck auswerten, um zu dem Dateinamen den Inhalt zu liefern.

Getter-Methoden als Function über Methodenreferenzen

Methodenreferenzen gehören zu den syntaktisch knappsten Sprachmitteln von Java. In Kombination mit Gettern ist ein Muster abzulesen, das oft in Code zu sehen ist. Zunächst noch einmal zur Wiederholung von Function und der Nutzung bei Methodenreferenzen:

Function<String,String> func1a = (String s) -> s.toUpperCase();

Function<String,String> func1b = String::toUpperCase;

Function<Point,Double> func2a = (Point p) -> p.getX();

Function<Point,Double> func2b = Point::getX;

System.out.println( func1b.apply( "jocelyn" ) ); // JOCELYN

System.out.println( func2b.apply( new Point( 9, 0 ) ) ); // 9.0

Dass Function auf die gegebene Methodenreferenz passt, ist auf den ersten Blick unverständlich, da die Signaturen von toUpperCase() und getX() keinen Parameter deklarieren, also im üblichen Sinne keine Funktionen sind, in die etwas hineinkommt und aus denen wieder herauskommt. Wir haben es hier aber mit einem speziellen Fall zu tun, denn die in der Methodenreferenz genannten Methoden sind

  • nicht statisch – wie Math::max –, und

  • es ist auch keine Referenz – wie System.out::print – im Spiel,

sondern hier wird der Compiler eine Objektmethode auf genau dem Objekt aufrufen, das als erstes Argument der funktionalen Schnittstelle übergeben wurde. (Diesen Satz bitte zweimal lesen!)

Damit ist Function ein praktischer Typ in allen Szenarien, in denen irgendwie über Getter Zustände erfragt werden, wie es etwa bei einem Comparator öfter vorkommt. Hier ist eine statische Methode (die Generics einmal ausgelassen) Comparator<…> Comparator.comparing(Function<…> keyExtractor) sehr nützlich.

[zB]  Beispiel

Besorge eine Liste von Paketen, die vom Klassenlader zugänglich sind, und sortiere sie nach Namen:

List<Package> list = Arrays.asList( Package.getPackages() );

Collections.sort( list, Comparator.comparing( Package::getName ) );

System.out.println( list ); // [package java.io, ... sun.util.locale ...

Default-Methoden in Function

Die funktionale Schnittstelle schreibt nur eine Methode apply(…) vor, deklariert jedoch noch drei zusätzliche Default-Methoden:

interface java.util.function.Function<T,R>
  • static <T> Function<T,T> identity()

    Liefert eine Funktion, die immer die Eingabe als Ergebnis liefert, implementiert als return t -> t;.

  • default <V> Function<T,V> andThen(Function<? super R,? extends V> after)

    Entspricht t -> after.apply(apply(t)).

  • default <V> Function<V,R> compose(Function<? super V,? extends T> before)

    Entspricht v -> apply(before.apply(v)).

identity() scheint auf den ersten Blick sinnlos zu sein, erfüllt aber einen Zweck, wenn in der API eine Function erforderlich ist, bei der sich nichts verändern, die Eingabe also die Ausgabe sein soll.

Die Methoden andThen(…) und compose(…) unterscheiden sich darin, in welcher Reihenfolge die Funktionen aufgerufen werden. Das Gute ist, dass die Parameternamen (before, after) klarmachen, was hier in welcher Reihenfolge aufgerufen wird.

[zB]  Beispiel
Function<String, String> f1 = s -> "~" + s + "~";

Function<String, String> f2 = s -> "<" + s + ">";

System.out.println( f1.andThen( f2 ).apply( ":)" ) ); // <~:)~>

System.out.println( f2.andThen( f1 ).apply( ":)" ) ); // ~<:)>~

System.out.println( f1.compose( f2 ).apply( ":)" ) ); // ~<:)>~

System.out.println( f2.compose( f1 ).apply( ":)" ) ); // <~:)~>

Function versus Consumer/Predicate

Im Grunde lässt sich alles als Function darstellen, denn

  • ein Consumer<T> lässt sich auch als Function<T,Void> verstehen (es geht etwas hinein, aber nichts kommt heraus),

  • ein Predicate<T> als Function<T,Boolean> und

  • ein Supplier<T> als Function<Void,T>.

Dennoch erfüllen diese speziellen Typen ihren Zweck, denn je genauer der Typ ist, desto besser.

Function ist kein Basistyp von Consumer oder Supplier, da sich »keine Rückgabe« oder »kein Parameter« nicht durch einen generischen Typ ausdrücken kann, der bei Function<T,R> möglich wäre.

UnaryOperator

Es gibt auch eine weitere Schnittstelle im java.util.function-Paket, die Function spezialisiert, und zwar UnaryOperator. Ein UnaryOperator ist eine spezielle Function, bei der die Typen für »Eingang« und »Ausgang« gleich sind.

interface java.util.function.UnaryOperator<T>

extends Function<T,T>
  • static <T> UnaryOperator<T> identity()

    Liefert den Identitätsoperator, der alle Eingaben auf die Ausgaben abbildet.

Die generischen Typen machen deutlich, dass der Typ des Methodenparameters gleich dem Ergebnistyp ist. Bis auf identity() gibt es keine weitere Funktionalität; die Schnittstelle dient lediglich zur Typdeklaration.

An einigen Stellen der Java-Bibliothek kommt dieser Typ auch vor, etwa bei der Methode replaceAll(UnaryOperator) der List-Typen.

[zB]  Beispiel

Verdopple jeden Eintrag in der Liste:

List<Integer> list = Arrays.asList( 1, 2, 3 );

list.replaceAll( e -> e * 2 );

System.out.println( list ); // [2, 4, 6]
 

Zum Seitenanfang

12.5.5    Ein bisschen Bi … Zur vorigen ÜberschriftZur nächsten Überschrift

»Bi« ist eine bekannte lateinische Vorsilbe für »zwei«, was übertragen auf die Typen aus dem Paket java.util.function bedeutet, dass statt eines Arguments zwei übergeben werden können.

Typ

Schnittstelle

Operation

Konsument

Consumer<T>

void accept(T t)

BiConsumer<T,U>

void accept(T t, U u)

Funktion

Function<T,R>

R apply(T t)

BiFunction<T,U,R>

R apply(T t, U u)

Prädikat

Predicate<T>

boolean test(T t)

BiPredicate<T,U>

boolean test(T t, U u)

Tabelle 12.13    Ein- und Zwei-Argument-Methoden im Vergleich

Die Bi-Typen haben mit den Nicht-Bi-Typen keine Typbeziehung.

BiConsumer

Der BiConsumer deklariert die Methode accept(T, U) mit zwei Parametern, die jeweils unterschiedliche Typen tragen können. Haupteinsatzpunkt des Typs in der Java-Standardbibliothek sind Assoziativspeicher, die Schlüssel und Werte an accept(…) übergeben. So deklariert Map die Methode:

interface java.util.Map<K,V>
  • default void forEach(BiConsumer<? super K,? super V> action)

    Läuft den Assoziativspeicher ab und ruft auf jedem Schlüssel-Wert-Paar die accept(…)-Methode vom übergebenen BiConsumer auf.

[zB]  Beispiel

Gib die Temperaturen der Städte aus:

Map<String,Integer> map = new HashMap<>();

map.put( "Manila", 25 );

map.put( "Leipzig", -5 );

map.forEach( (k, v) -> System.out.printf("%s hat %d Grad%n", k, v) );

Ein BiConsumer besitzt eine Default-Methode andThen(…), wie auch der Consumer sie zur Verkettung deklariert.

interface java.util.function.BiConsumer<T,U>
  • default BiConsumer<T,U> andThen(BiConsumer<? super T,? super U> after)

    Verknüpft den aktuellen BiConsumer mit after zu einem neuen BiConsumer.

BiFunction und BinaryOperator

Eine BiFunction ist eine Funktion mit zwei Argumenten, während eine normale Function nur ein Argument annimmt.

[zB]  Beispiel

Nutzung von Function und BiFunction mit Methodenreferenzen:

Function<Double,Double> sign = Math::abs;

BiFunction<Double,Double,Double> max = Math::max;

Die Java-Bibliothek greift viel öfter auf Function zurück als auf BiFunction. Der häufigste Einsatz findet sich in der Standardbibliothek rund um Assoziativspeicher, bei denen Schlüssel und Wert an eine BiFunction übergeben werden.

[zB]  Beispiel

Konvertiere alle assoziierten Werte einer HashMap in Großbuchstaben:

Map<Integer,String> map = new HashMap<>();

map.put( 1, "eins" ); map.put( 2, "zwEi" );

System.out.println( map ); // {1=eins, 2=zwEi}

BiFunction<Integer,String,String> func = (k, v) -> v.toUpperCase();

map.replaceAll( func );

System.out.println( map ); // {1=EINS, 2=ZWEI}

Ist bei einer Function der Typ derselbe, bietet die Java-API dafür den spezielleren Typ UnaryOperator. Sind bei einer BiFunction alle drei Typen gleich, bietet sich BinaryOperator an – zum Vergleich:

  • interface UnaryOperator<T> extends Function<T,T>

  • interface BinaryOperator<T> extends BiFunction<T,T,T>

[zB]  Beispiel

BiFunction und BinaryOperator:

BiFunction<Double,Double,Double> max1 = Math::max;

BinaryOperator<Double> max2 = Math::max;

BinaryOperator spielt bei sogenannten Reduktionen eine große Rolle, wenn zum Beispiel aus zwei Werten einer wird, wie bei Math.max(…); auch das beleuchtet der Abschnitt über die Stream-API später genauer.

Die Schnittstelle BiFunction deklariert genau eine Default-Methode:

interface java.util.function.BiFunction<T,U,R>

extends Function<T,T>
  • default <V> BiFunction<T,U,V> andThen(Function<? super R,? extends V> after)

BinaryOperator dagegen wartet mit zwei statischen Methoden auf:

public interface java.util.function.BinaryOperator<T>

extends BiFunction<T,T,T>
  • static <T> BinaryOperator<T> maxBy(Comparator<? super T> comparator)

  • static <T> BinaryOperator<T> minBy(Comparator<? super T> comparator)

    Liefert einen BinaryOperator, der das Maximum/Minimum bezüglich eines gegebenen comparator liefert.

BiPredicate

Ein BiPredicate testet zwei Argumente und verdichtet sie zu einem Wahrheitswert. Wie Predicate deklariert auch BiPredicate drei Default-Methoden and(…), or(…) und negate(…), wobei natürlich eine statische isEqual(…)-Methode wie bei Predicate in BiPredicate fehlt. Für BiPredicate gibt es in der Java-Standardbibliothek nur eine Verwendung bei einer Methode zum Finden von Dateien – der Gebrauch ist selten, zudem ja auch ein Prädikat immer eine Funktion mit boolean-Rückgabe ist, sodass es eigentlich für diese Schnittstelle keine zwingende Notwendigkeit gibt.

[zB]  Beispiel

Bei BiXXX und zwei Argumenten hört im Übrigen die Spezialisierung auf, es gibt keine Typen TriXXX, QuaddXXX … Das ist in der Praxis auch nicht nötig, denn zum einen kann oftmals eine Reduktion stattfinden – so ist etwa max(1, 2, 3) gleich max(1, max(2, 3)) –, und zum anderen kann auch der Parametertyp eine Sammlung sein, wie in Function<List<Integer>, Integer> max.

 

Zum Seitenanfang

12.5.6    Funktionale Schnittstellen mit Primitiven Zur vorigen ÜberschriftZur nächsten Überschrift

Die bisher vorgestellten funktionalen Schnittstellen sind durch die generischen Typparameter sehr flexibel, aber was fehlt, sind Signaturen mit Primitiven – Java hat das »Problem«, dass Generics nur mit Referenztypen funktionieren, nicht aber mit primitiven Typen. Aus diesem Grund gibt es von fast allen Schnittstellen aus dem java.util.function-Paket vier Versionen: eine generische für beliebige Referenzen sowie Versionen für die Typen int, long und double. Die API-Designer wollten gerne die Wrapper-Typen außen vor lassen und gewisse primitive Typen unterstützen, auch aus Performance-Gründen, um nicht immer ein Boxing durchführen zu müssen.

Tabelle 12.14 gibt einen Überblick über die funktionalen Schnittstellen, die alle keinerlei Vererbungsbeziehungen zu anderen Schnittstellen haben.

Funktionale Schnittstelle

Funktionsdeskriptor

XXXSupplier

BooleanSupplier

boolean getAsBoolean()

IntSupplier

int getAsInt()

LongSupplier

long getAsLong()

DoubleSupplier

double getAsDouble()

XXXConsumer

IntConsumer

void accept(int value)

LongConsumer

void accept(long value)

DoubleConsumer

void accept(double value)

ObjIntConsumer<T>

void accept(T t, int value)

ObjLongConsumer<T>

void accept(T t, long value)

ObjDoubleConsumer<T>

void accept(T t, double value)

XXXPredicate

IntPredicate

boolean test(int value)

LongPredicate

boolean test(long value)

DoublePredicate

boolean test(double value)

XXXFunction

DoubleToIntFunction

int applyAsInt(double value)

IntToDoubleFunction

double applyAsDouble(int value)

LongToIntFunction

int applyAsInt(long value)

IntToLongFunction

long applyAsLong(int value)

DoubleToLongFunction

long applyAsLong(double value)

LongToDoubleFunction

double applyAsDouble(long value)

IntFunction<R>

R apply(int value)

LongFunction<R>

R apply(long value)

DoubleFunction<R>

R apply(double value)

ToIntFunction<T>

int applyAsInt(T t)

ToLongFunction<T>

long applyAsLong(T t)

ToDoubleFunction<T>

double applyAsDouble(T t)

ToIntBiFunction<T,U>

int applyAsInt(T t, U u)

ToLongBiFunction<T,U>

long applyAsLong(T t, U u)

ToDoubleBiFunction<T,U>

double applyAsDouble(T t, U u)

XXXOperator

IntUnaryOperator

int applyAsInt(int operand)

LongUnaryOperator

long applyAsLong(long operand)

DoubleUnaryOperator

double applyAsDouble(double operand)

IntBinaryOperator

int applyAsInt(int left, int right)

LongBinaryOperator

long applyAsLong(long left, long right)

DoubleBinaryOperator

double applyAsDouble(double left, double right)

Tabelle 12.14    Spezielle funktionale Schnittstellen für primitive Werte

Statische und Default-Methoden

Einige generisch deklarierte funktionale Schnittstellen-Typen besitzen Default-Methoden bzw. statische Methoden, und Ähnliches findet sich auch bei den primitiven funktionalen Schnittstellen wieder:

  • Die XXXConsumer-Schnittstellen deklarieren default XXXConsumer andThen(XXXConsumer after), aber nicht die ObjXXXConsumer-Typen. Sie besitzen keine Default-Methode.

  • Die XXXPredicate-Schnittstellen deklarieren:

    • default XXXPredicate negate()

    • default XXXPredicate and(XXXPredicate other)

    • default XXXPredicate or(XXXPredicate other)

  • Jeder XXXUnaryOperator besitzt:

    • default XXXUnaryOperator andThen(XXXUnaryOperator after)

    • default XXXUnaryOperator compose(XXXUnaryOperator before)

    • static XXXUnaryOperator identity()

  • BinaryOperator hat zwei statische Methoden maxBy(…) und minBy(…), die es nicht in der primitiven Version XXXBinaryOperator gibt, da kein Comparator bei primitiven Vergleichen nötig ist.

  • Die XXXSupplier-Schnittstellen deklarieren keine statischen Methoden oder Default-Methoden, genauso wie es auch Supplier nicht tut.

 


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