Illustration einer tropischen Insel mit Palmen und einem hohen Berg

Java Blog für Programmierer

Funktionale Schnittstelle in Java 8 aus java.util.function

· Christian Ullenboom

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) → voidDoubleConsumer(double) → voidBiConsumer<T, U>(T, U) → voidSupplier<T>() → TBooleanSupplier() → booleanPredicate<T>(T) → booleanLongPredicate(long) → booleanBiPredicate<T, U>(T, U) → booleanFunction<T, R>(T) → RLongToDoubleFunction(long) → doubleBiFunction<T, U, R>(T, U) → RUnaryOperator<T>(T) → TDoubleBinaryOperator(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  Consumer measuringConsumer( Consumer 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 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 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 MethodePredicate&lt;Character&gt;isDigit(), mit der NutzungCharacter.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 isDigit = Character::isDigit;
List 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 isDigit = Character::isDigit;
Predicate isNotDigit = isDigit.negate();
List 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 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 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 func2a = (String s) -> s.toUpperCase();
Function func2b = String::toUpperCase;
Function func1a = (Point p) -> p.getX();
Function 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 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 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**OperationKonsumentConsumer<T>void accept(T t)BiConsumer<T,U>void accept(T t, U u)FunktionFunction<T,R>R apply(T t)``BiFunction<T,U,R>R apply(T t, U u)PrädikatPredicate<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 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 sign = Math::abs;
BiFunction 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 map = new HashMap();
map.put( 1, "eins" ); map.put( 2, "zwEi" );
System.out.println( map );  // {1=eins, 2=zwEi}
BiFunction 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 max1 = Math::max;
BinaryOperator             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**XXXSupplierBooleanSupplierboolean getAsBoolean()IntSupplierint getAsInt()LongSupplierlong getAsLong()DoubleSupplierdouble getAsDouble()XXXConsumerIntConsumervoid accept(int value)LongConsumervoid accept(long value)DoubleConsumervoid 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)XXXPredicateIntPredicateboolean test(int value)LongPredicateboolean test(long value)DoublePredicateboolean test(double value)XXXFunctionDoubleToIntFunctionint applyAsInt(double value)IntToDoubleFunctiondouble applyAsDouble(int value)LongToIntFunctionint applyAsInt(long value)IntToLongFunctionlong applyAsLong(int value)DoubleToLongFunctionlong applyAsLong(double value)LongToDoubleFunctiondouble 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)XXXOperatorIntUnaryOperatorint applyAsInt(int operand)LongUnaryOperatorlong applyAsLong(long operand)DoubleUnaryOperatordouble applyAsDouble(double operand)IntBinaryOperatorint applyAsInt(int left, int right)LongBinaryOperatorlong applyAsLong(long left, long right)DoubleBinaryOperatordouble 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 …

Kategorien: Insel, Java 8

2 Kommentare

Konrad Demmel ·

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. richtig: ... dass statt ...

Michael ·

Die Abbildung des DoubleBinaryOperator im "ersten Überblick" (erste Tabelle) ist falsch. Sie müsste lauten: (double, double) → double

Die bisherigen Kommentare wurden aus WordPress übernommen. Neue Kommentare sind derzeit geschlossen.

← Alle Beiträge