Funktionale Schnittstelle in Java 8 aus java.util.function

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

Beispiele einiger vordefinierter funktionaler Schnittstellen

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

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 dann 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 Code-Block nach der Art doSomethingWith(myConsumer) annehmen, und ihn etwa in einem Hintergrund-Thread abzuarbeiten, oder wiederholend auszuführen, oder nach einer erlaubten Maximaldauer abbrechen, oder die Zeit messen, oder, oder, oder …

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

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

Beispiel: Gib jedes Element einer Liste auf der Konsole aus:

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

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

Prädikate und java.util.function.Predicate

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, einem Zeichen, eine Wahrheitsaussage fällen kann.

Flexibler sind Prädikate, wenn sie als Objekte repräsentiert werden, weil sie dann an unterschiedliche Stellen weitergegeben werden können. Wenn etwa über ein Prädikat bestimmt wird, was aus einer Sammlung gelöscht werden soll oder ob mindestens ein Element in einer Sammlung ist, welches ein Prädikat erfüllt.

Das java.util.function-Paket[1] 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.

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 Methoden-Referenzen geben zum Glück die Flexibilität, dass problemlos existierende Methoden als Lambda-Ausdrücke genützt 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ösch-Methoden, um in Sammlungen Elemente zu spezifizieren, die gelöscht oder nach denen gefiltert werden sollen.
  • Bei den Default-Methoden der Predicate-Schnittstelle selbst, um Prädikate zu verknüpfen.
  • Bei regulären Ausdrücken: ein Pattern liefert mit asPredicte() 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.

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 );

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)
    Und/Oder-Verknüpfen das aktuelle Prädikat mit einem anderen Prädikat.
  • static <T> Predicate<T> isEqual(Object targetRef)
    Liefert ein neues Prädikat welches einen Gleichheitstest mit targetRef vornimmt, im Grunde return ref -> Objects.equals(ref, targetRef).

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

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

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

Beispiel:Eine Funktion zur Bestimmung des Maximums:

Function<Double, Double> max = a -> Math.abs( a );  // alternativ Math::abs
System.out.println( max.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 Methoden-Referenzen 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 vom Function-Typ. Es finden sich einige wenige Beispiele bei Objekt-Vergleichen (Comparator), im Paket für Nebenläufigkeiten und bei Assoziativspeichern. Im Abschnitt über die Stream-API werden wir daher viele weitere Beispiele kennenlernen.

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.

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 ); }
    } );
  }
}

Zu der Methode kommt das Kapitel über Datenstrukturen noch einmal zurück, das Beispiel soll nur eine Idee 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 Methoden-Referenzen

Methoden-Referenzen gehören zu den stärksten Sprachmitteln von Java 8. In Kombination mit Gettern ist ein Muster abzulesen, welches oft in Code zu sehen ist. Zunächst noch einmal zur Wiederholung von Function und der Nutzung bei Methoden-Referenzen:

Function<String, String> func2a = (String s) -> s.toUpperCase();
Function<String, String> func2b = String::toUpperCase;

Function<Point, Double> func1a = (Point p) -> p.getX();
Function<Point, Double> func1b = Point::getX;

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

Dass Function auf die gegebene Methoden-Referenz 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 Methoden-Referenz genannten Methoden sind a) nicht statisch – wie Math::max – und b) es ist auch keine Referenz – wie System.out::print – im Spiel, sondern hier wird der Compiler eine Objektmethode auf genau dem Objekt aufrufen was 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 öfters vorkommt. Hier ist eine statische Methode  – die Generics einmal ausgelassen – Comparator<…> Comparator.comparing(Function<…> keyExtractor) sehr nützlich.

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 also 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 selbst „compose“ wenig aussagt.

Function vs. Consumer/Predicate , UnaryOperator

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>,
  • ein Supplier<T> als Function<Void,T>.

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

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.

Beispiel: Verdopple in der Liste jeden Eintrag.

List<Integer> list = Arrays.asList( 1, 2, 3 );
list.replaceAll( e -> e * 2 );
System.out.println( list );  // [2, 4, 6]

Ein bisschen Bi …

Bi ist eine bekannte lateinische Vorsilbe für „zwei“, was übertragen auf die Typen aus java.util.function bedeutet, das statt einem Argument 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)

Ein-/Zwei-Argument Methoden Vergleich

Die Bi-Typen haben mit den nicht-Bi-Typen keine Typbeziehung.[2]

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-Werte-Paar die accept(…)-Methode vom übergebenen BiConsumer auf.

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 neunen BiConsumer.

BiFunction und BinaryOperator

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

Beispiel: Nutzung von Function und BiFunction mit Methoden-Referenzen.

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

Die Java-Bibliothek greift viel öfters 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.

Beispiel: Konvertiere alle assoziierten Werte einer HashMap in 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>

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 wie bei max aus zwei Werten einer wird, 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 zwei statische Methoden:

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

Beispiel: Bei BiXXX und zwei Argumenten hört im Übrigen die Spezialisierung auf, es gibt keine Typen TriXXX, QuardXXX, …. 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 Zweiten kann auch der Parametertyp eine Sammlung sein, wie in Function<List<Integer>, Integer> max.

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 funktioniert, 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.

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

Funktionale Schnittstelle Funktions-Deskriptor
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)

Spezielle funktionale Schnittstellen für primitive Werte

Statische und Default-Methoden

Einige generisch deklarierte funktionale Schnittstellen Typen besitzen Default-Methoden bzw. statische Methoden besitzen, 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 IntPredicate or(XXXPredicate other).

Jeder XXXUnaryOperator besitzt

  • default XXXUnaryOperator andThen(IntUnaryOperator after)
  • default XXXUnaryOperator compose(XXXUnaryOperator before) und
  • static IntUnaryOperator identity().

BinaryOperator hat zwei statischen 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 statische- oder Default-Methoden, genauso wie es auch Supplier nicht tut.



[1]      Achtung, in javax.sql.rowset gibt es ebenfalls eine Schnittstelle Predicate.

[2]      Irgendwie finden das manche Leser lustig …

Über Christian Ullenboom

Ich bin Christian Ullenboom und Autor der Bücher ›Java ist auch eine Insel. Einführung, Ausbildung, Praxis‹ und ›Java SE 8 Standard-Bibliothek. Das Handbuch für Java-Entwickler‹. Seit 1997 berate ich Unternehmen im Einsatz von Java. Sun ernannte mich 2005 zum ›Java-Champion‹.

Ein Gedanke zu „Funktionale Schnittstelle in Java 8 aus java.util.function

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.