12.5 Funktionale Schnittstellen aus dem java.util.function-Paket
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 |
12.5.1 Blöcke mit Code und die funktionale Schnittstelle Consumer
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:
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.
12.5.2 Supplier
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.
12.5.3 Prädikate und java.util.function.Predicate
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 ) );
12.5.4 Funktionen über die funktionale Schnittstelle java.util.function.Function
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.
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]
12.5.5 Ein bisschen Bi …
»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.
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) |
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.
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.
12.5.6 Funktionale Schnittstellen mit Primitiven
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) |
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) |
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.