Funktionale Interfaces
- Interfaces mit genau einer abstrakten Methode
- Ausnahme: Überschreiben von konkreten Methoden der Klasse
Object(z.B.equals) - Werden mit
@FunctionalInterfaceannotiert - Ziel: Wird zu Lambda-Ausdruck
Lambda-Ausdrücke
Syntax
( Parameterliste ) -> { Anweisungen }- Die Parameter können sowohl mit, als auch ohne Typen angegeben werden
- Bei einem Parameter können die runden Klammern weggelassen werden
- Wenn im Codeblock nur eine Anweisung steht, können die geschwiffenen Klammern weggelassen werden
- Lambda-Ausdrücke können auch Methodenreferenzen sein, z.B.
System.out::println
Beispiele
() -> 5
x -> 2 * x
(x, y) -> x + y
(int x, int y) -> x + y
String::compareToAnwendung
- Lambda-Austrücke können überall dort verwendet werden, wo funktionale Interfaces erwartet werden.
- Der Compiler ersetzt den Lambda-Ausdruck durch das Objekt einer anonymen Klasse (= das funktionale Interface mit implementierter Funktion duch den Lambda-Ausdruck), die im Kontext erwartet wird (Zieltyp).
Funktionsweise
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
// Gegeben ist dieser Lambda-Ausdruck:
numbers.forEach(number -> System.out.println(number));
// Die vollständige Methodensignatur von forEach lautet:
void forEach(Consumer<? super E> action)
// Daher wird der Lambda-Ausdruck wie folgt ersetzt:
numbers.forEach(new Consumer<Integer>() {
@Override
public void accept(Integer number) {
System.out.println(number);
}
});Scope
- Lambda-Ausdrücke können auf die lokalen Variablen des umgebenden Blocks zugreifen, aber nur, wenn diese
finaloder effectively final sind - Lambda-Ausdrücke haben keinen eigenen Sichtbarkeitsbereich, daher müssen sich die Namen der Lambda-Parameter von den Variablennamen im umgebenden Block unterscheiden.
Das Paket java.util.function
Consumer
Ein oder mehrere Inputs, kein Output.
- Mit einem Argument:
Consumer<T>,IntConsumer,LongConsumer,DoubleConsumer- Lambda:
T -> void, bzw.int -> void, …
- Mit zwei Argumenten:
BiConsumer<T,U>- Lambda:(T, U ) -> voidObjIntConsumer<T>- Lambda:(T, int ) -> voidObjLongConsumer<T>- s.o.ObjDoubleConsumer<T>- s.o.
// Beispiel:
Consumer<String> c = (String s) -> System.out.println(s);
c.accept("Hallo");Supplier
Kein Input, nur ein Output.
Supplier<T>,IntSupplier,LongSupplier,DoubleSupplier,BooleanSupplier- Lambda:
() -> T, bzw.() -> int, …
// Beispiel:
Supplier<Random> randomSupplier = () -> new Random(4711);
IntSupplier randomIntSupplier = () -> new Random().nextInt();Function
Wandelt einen oder zwei Inputs in einen Output um
Function<T,R>,IntFunction<R>, …ToIntFunction<T>,ToLongFunction<T>, …IntToLongFunction,IntToDoubleFunction,LongToIntFunction, …BiFunction<T,U,R>,ToIntBiFunction<T,U>,ToLongBiFunction<T,U>, …
// Beispiel:
ToIntFunction<String> f1 = string -> string.length();
IntFunction<double[]> f2 = länge -> new double[länge];Operator
Funktionen, bei denen Input und Output vom selben Typ sind
UnaryOperator<T>,IntUnaryOperator, … Lambda:T -> TBinaryOperator<T>,IntBinaryOperator, … Lambda:(T, T) -> T
// Beispiel:
IntBinaryOperator add = (a, b) -> a + b;Predicate
Funktionen, bei denen Output vom Typ boolean ist
Predicate<T>,IntPredicate, …BiPredicate<T,U>. Dieses hat keine primitiven Varianten.
// Beispiel:
IntPredicate p = c -> Character.isAlphabetic(c);Lambdas und Exceptions
Checked Exceptions müssen direkt vom Lambda verarbeitet werden, da das Lambda (aka die Funktion im funktionalen Interface) keine throws-Klausel hat:
Predicate<Path> empty = datei -> {
try {
return Files.size(datei) == 0;
} catch (IOException e) {
throw new RuntimeException(e);
}
};Hier wird die checked IoException in eine unchecked RuntimeExcption eingewickelt, die auch ohne throws weitergegeben werden kann.
Beispiele
Iteration über List
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
numbers.forEach(number -> System.out.println(number));Iteration über Map
Map<Integer, String> students = ...;
students.forEach((key, value) ->
System.out.println(key + ": " + value));Iteration mit System.out::println
List<String> namen = List.of("Clara", "Raphael", "Florian");
namen.forEach(System.out::println);Kurzform von Comparatoren
Arrays.sort(w, new Comparator<>() {
@Override public int compare(String s1, String s2) {
return Integer.compare(s1.length(), s2.length());
}
});Dieser Code lässt sich mit Lambda-Ausdrücken vereinfachen:
Arrays.sort(w, (s1, s2) -> Integer.compare(s1.length(), s2.length()));Und dafür gibt es dann wieder eine schon definierte Methode (zwar kein Lambda-Ausdruck mehr aber trotzdem wissenswert):
Arrays.sort(w, Comparator.comparingInt(String::length));Der Container Atomic Integer
Siehe auch
Funktionale Interfaces anderswo
In Arrays-Mathoden
<T> void setAll(T[] array, IntFunction<T> generator)- Setzt
arrayauf Werte, diegeneratorliefert. - Dafür übergibt
setAllan dengeneratorden Index des Elements.
// Beispiel: Befülle Array mit Quadratwurzeln vom Index
double[] a = new double[5];
Arrays.setAll(a, Math::sqrt);
System.out.println(Arrays.toString(a));
// Ausgabe: 0.0, 1.0, 1.41421, 1.73205, 2.0// Beispiel: Wandle alle Elemente zu Uppercase um:
String[] namen = {"Clara", "Raphael", "Florian", "Julia"};
Arrays.setAll(namen, i -> namen[i].toUpperCase());
System.out.println(Arrays.toString(namen));
// Ausgabe: CLARA, RAPHAEL, FLORIAN, JULIAIn Comparator-Methoden
- Comparatoren haben hilfreiche statische und default-Methoden, die das Coden erleichtern:
// Extrahiert mit hilfe von keyExtractor einen int-Schlüssel
// aus T, der dann zum vergleichen verwendet wird.
static <T> Comparator<T> comparingInt(ToIntFunction<T> keyExtractor)
// Liefert einen Comparator, der im gleichheitsfall des ersten
// den zweiten verwendet.
default Comparator<T> thenComparing(Comparator<T> other)
// Liefert einen Comparator, der im Gleichheitsfall nach
// dem extrahierten int-Schlüssel vergleicht.
default Comparator<T> thenComparingInt(ToIntFunction<T> keyExtractor)Anwendung:
Arrays.sort(namen, (s, t) ->
Integer.compare(s.length(), t.length())
);
// wird zu:
Arrays.sort(namen, Comparator.comparingInt(string ->
string.length())
);
// dafür gibt es auch wieder einen shorthand:
Arrays.sort(namen, Comparator.comparingInt(String::length));
// mit Hilfe von statischen Imports lässt sich der Klassenname
// auch noch wegkürzen:
Arrays.sort(namen, comparingInt(String::length));
// Beispiel mit thenComparing:
Arrays.sort(a, comparingInt(String::length)
.thenComparing(naturalOrder())
);