Rheinwerk Computing < openbook > Rheinwerk Computing - Professionelle Bücher. Auch für Einsteiger.
Professionelle Bücher. Auch für Einsteiger. 
Inhaltsverzeichnis
Vorwort
1 Java ist auch eine Sprache
2 Imperative Sprachkonzepte
3 Klassen und Objekte
4 Der Umgang mit Zeichenketten
5 Eigene Klassen schreiben
6 Objektorientierte Beziehungsfragen
7 Ausnahmen müssen sein
8 Äußere.innere Klassen
9 Besondere Typen der Java SE
10 Generics<T>
11 Lambda-Ausdrücke und funktionale Programmierung
12 Architektur, Design und angewandte Objektorientierung
13 Komponenten, JavaBeans und Module
14 Die Klassenbibliothek
15 Einführung in die nebenläufige Programmierung
16 Einführung in Datenstrukturen und Algorithmen
17 Einführung in grafische Oberflächen
18 Einführung in Dateien und Datenströme
19 Einführung ins Datenbankmanagement mit JDBC
20 Einführung in <XML>
21 Testen mit JUnit
22 Bits und Bytes und Mathematisches
23 Die Werkzeuge des JDK
A Java SE-Paketübersicht
Stichwortverzeichnis


Download:

- Beispielprogramme, ca. 35,4 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 11 Lambda-Ausdrücke und funktionale Programmierung
Pfeil 11.1 Code = Daten
Pfeil 11.2 Funktionale Schnittstellen und Lambda-Ausdrücke im Detail
Pfeil 11.2.1 Funktionale Schnittstellen
Pfeil 11.2.2 Typ eines Lambda-Ausdrucks ergibt sich durch Zieltyp
Pfeil 11.2.3 Annotation @FunctionalInterface
Pfeil 11.2.4 Syntax für Lambda-Ausdrücke
Pfeil 11.2.5 Die Umgebung der Lambda-Ausdrücke und Variablenzugriffe
Pfeil 11.2.6 Ausnahmen in Lambda-Ausdrücken
Pfeil 11.2.7 Klassen mit einer abstrakten Methode als funktionale Schnittstelle? *
Pfeil 11.3 Methodenreferenz
Pfeil 11.3.1 Varianten von Methodenreferenzen
Pfeil 11.4 Konstruktorreferenz
Pfeil 11.4.1 Standard- und parametrisierte Konstruktoren
Pfeil 11.4.2 Nützliche vordefinierte Schnittstellen für Konstruktorreferenzen
Pfeil 11.5 Implementierung von Lambda-Ausdrücken *
Pfeil 11.6 Funktionale Programmierung mit Java
Pfeil 11.6.1 Programmierparadigmen: imperativ oder deklarativ
Pfeil 11.6.2 Funktionale Programmierung und funktionale Programmiersprachen
Pfeil 11.6.3 Funktionale Programmierung in Java am Beispiel vom Comparator
Pfeil 11.6.4 Lambda-Ausdrücke als Funktionen sehen
Pfeil 11.7 Funktionale Schnittstelle aus dem java.util.function-Paket
Pfeil 11.7.1 Blöcke mit Code und die funktionale Schnittstelle java.util.function.Consumer
Pfeil 11.7.2 Supplier
Pfeil 11.7.3 Prädikate und java.util.function.Predicate
Pfeil 11.7.4 Funktionen und die allgemeine funktionale Schnittstelle java.util.function.Function
Pfeil 11.7.5 Ein bisschen Bi …
Pfeil 11.7.6 Funktionale Schnittstellen mit Primitiven
Pfeil 11.8 Optional ist keine Nullnummer
Pfeil 11.8.1 Optional-Typ
Pfeil 11.8.2 Primitive optionale Typen
Pfeil 11.8.3 Erstmal funktional mit Optional
Pfeil 11.9 Was ist jetzt so funktional?
Pfeil 11.10 Zum Weiterlesen
 

Zum Seitenanfang

11.7Funktionale Schnittstelle 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. Ein erster Ü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) → boolean

Tabelle 11.9Beispiele einiger vordefinierter funktionaler Schnittstellen

 

Zum Seitenanfang

11.7.1Blöcke mit Code und die funktionale Schnittstelle java.util.function.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).

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 gedacht als Endglied einer Kette, in der zum Beispiel Dateien in eine Datei geschrieben werden, die vorher verarbeitet wurden. Diese Seiteneffekte sind beabsichtigt, da sie nach einer Kette von seiteneffektfreien Operationen stehen.

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, oder kann ihn nach einer erlaubten Maximaldauer abbrechen oder die Zeit messen oder, oder, oder …

[zB]Beispiel

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

Listing 11.7Consumers.java

import java.util.function.*;

import java.util.logging.Logger;

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:

Consumer<Void> wrap = measuringConsumer( Void -> System.out.println( "Test" ) );

wrap.accept( null );

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.

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.

 

Zum Seitenanfang

11.7.2Supplier 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 Interna. Es können neue Objekte sein, immer die gleichen Objekte (Singleton) oder Objekte aus einem Cache.

 

Zum Seitenanfang

11.7.3Prä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 mit Character.isDigit('a'), ob das Zeichen »a« eine Ziffer ist, wird mit falsch beantwortet – isDigit ist also ein Prädikat, weil es über einen Gegenstand, ein Zeichen, eine Wahrheitsaussage fällen kann.

Prädikate als Objekte sind flexibel, denn Objekte lassen sich an unterschiedliche Stellen weitergeben. Wenn etwa ein Prädikat bestimmt, was aus einer Sammlung gelöscht werden soll oder ob mindestens ein Element in einer Sammlung ist, das ein Prädikat erfüllt.

Das java.util.function-Paket[ 210 ](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 geschrieben würde als Character.IS_DIGIT.test(…) 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 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.

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 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; ein Pattern liefert mit asPredicate() 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 Zeichen alle, die Ziffern sind (es bleiben nur Zeichen übrig, etwa Buchstaben):

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. Zusammenfassend:

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

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

  • 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 logisch Und/Oder.

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

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

[zB]Beispiel

Lösche aus einer Liste mit Zeichen alle die, 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 );

Prädikate aus Pattern

Die Pattern-Methode asPredicate() liefert ein Predicate<String>, sodass ein regulärer Ausdruck als Kriterium, zum Beispiel zum Filtern oder Löschen von Einträgen in Datenstrukturen, genutzt werden kann.

[zB]Beispiel

Lösche aus einer Liste alle Strings, die leer oder Zahlen sind:

List<String> list = new ArrayList<>( Arrays.asList( "Peaches", "", "25", "Geldof" ) );

list.removeIf( Pattern.compile( "\\d+" ).asPredicate().or( String::isEmpty ) );

System.out.println( list ); // [Peaches, Geldof]
 

Zum Seitenanfang

11.7.4Funktionen und die allgemeine 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.

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 dieser in den Assoziativspeicher gelegt werden.

Listing 11.8FileCache.java, FileCache

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 kommt Kapitel 16, »Einführung in Datenstrukturen und Algorithmen«, noch einmal zurück, das Beispiel soll nur eine Vorstellung 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, as 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, wo etwas reinkommt und wieder rauskommt. Wir haben es hier aber mit einem speziellen Fall zu tun, denn die in der Methodenreferenz genannten Methoden sind a) nicht statisch – wie Math::max –, und b) 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 bei allen Szenarien, bei 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 Pakten, 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 neue Funktion, die immer die Eingabe als Ergebnis liefert.

  • 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)).

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, wenn auch »compose« selbst wenig aussagt.

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 rein, aber nichts raus),

  • 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, desto besser.

UnaryOperator

Es gibt auch eine weitere Schnittstelle im java.util.function-Paket, die Function spezialisiert, und zwar UnaryOperator. Ein UnaryOperator ist eine spezielle Funktion, 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äts-Operator, 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

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

Bi ist eine bekannte lateinische Vorsilbe für »zwei«, was übertragen auf die Typen aus java.util.function bedeutet, das 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 11.10Ein-/Zwei-Argument-Methoden im Vergleich

Die Bi-Typen haben mit den Nicht-Bi-Typen keine Typbeziehung.[ 211 ](Irgendwie finden das manche Leser lustig … )

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 nach Großschreibung:

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 hier 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 so genannten 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 einer 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

11.7.6Funktionale 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 function-Paket vier Versionen: eine generische für beliebige Referenzen sowie Versionen für den Typ 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 11.11 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 11.11Spezielle 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.

 


Ihr Kommentar

Wie hat Ihnen das <openbook> gefallen? Wir freuen uns immer über Ihre freundlichen und kritischen Rückmeldungen.

>> Zum Feedback-Formular
<< zurück

 

 


Copyright © Rheinwerk Verlag GmbH 2017

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