1. Einleitung
Eine funktionale Schnittstelle ist eine Schnittstelle, die genau eine abstrakte Methode enthält (also eine Methode ohne Implementierung). Genau solche Schnittstellen können für eine kurze Schreibweise der Methodenimplementierung verwendet werden – in Java geschieht das mit Lambda-Ausdrücken (die behandeln wir später).
Warum nur eine Methode?
Weil eine funktionale Schnittstelle genau eine Operation beschreibt. Wenn es zwei oder mehr Methoden gäbe, wäre unklar, welche Methode genau implementiert werden soll. Die Regel ist daher einfach: eine Schnittstelle – eine abstrakte Methode.
Beispiele aus der Standardbibliothek
- Runnable – für Aufgaben in Threads (void run())
- Callable<V> – für Aufgaben, die ein Ergebnis zurückgeben (V call())
- Comparator<T> – zum Vergleichen von Objekten (int compare(T o1, T o2))
- Consumer<T> – „Verbraucher“ eines Wertes (void accept(T t))
- Supplier<T> – „Lieferant“ eines Wertes (T get())
- Function<T, R> – Funktion von T nach R (R apply(T t))
- Predicate<T> – Bedingungsprüfung (boolean test(T t))
Hier zum Beispiel die Schnittstelle Runnable:
public interface Runnable {
void run();
}
Und hier die Schnittstelle Comparator:
public interface Comparator<T> {
int compare(T o1, T o2);
// ... es gibt noch default- und static-Methoden, aber die abstrakte Methode gibt es nur einmal!
}
Wichtiger Punkt: default- und static-Methoden gelten nicht als abstrakt, daher kann es davon beliebig viele geben!
2. Die Annotation @FunctionalInterface
Java ist eine strenge, prinzipientreue Dame. Damit es keine Verwirrung gibt, kann man eine Schnittstelle mit der Annotation @FunctionalInterface ausdrücklich als funktional markieren. Das ist wie ein Aufkleber „Funktioniert nur mit einer Taste!“ – damit niemand etwas Überflüssiges hinzufügt.
@FunctionalInterface
public interface Operation {
int apply(int a, int b);
}
Wenn Sie nun versehentlich eine zweite abstrakte Methode hinzufügen, meldet der Compiler sofort einen Fehler:
@FunctionalInterface
public interface Oops {
void doIt();
void doSomethingElse(); // Fehler! Zwei abstrakte Methoden
}
Ist die Annotation zwingend?
Nein, das ist nicht erforderlich. Eine Schnittstelle ist auch ohne sie funktional, wenn sie genau eine abstrakte Methode enthält. Mit der Annotation zeigen Sie jedoch Ihre Absicht eindeutig und schützen sich vor versehentlichen Fehlern.
Darf man default und static-Methoden hinzufügen?
Ja, das darf man! Wichtig ist, dass es nur eine abstrakte Methode gibt. Alle übrigen Methoden dürfen default oder static sein, so viele Sie möchten.
Beispiel:
@FunctionalInterface
public interface FancyOperation {
int apply(int a, int b);
default void printInfo() {
System.out.println("Ich bin eine fancy Operation!");
}
static void description() {
System.out.println("Funktionale Schnittstelle für Arithmetik.");
}
}
3. Beispiele für Deklaration und Verwendung
Angenommen, Sie möchten eine Operation über zwei Zahlen beschreiben. So geht das:
@FunctionalInterface
public interface Operation {
int apply(int a, int b);
}
Diese Schnittstelle kann nun auf verschiedene Weise implementiert werden.
Implementierung über eine normale Klasse
public class SumOperation implements Operation {
@Override
public int apply(int a, int b) {
return a + b;
}
}
Verwendung:
Operation sum = new SumOperation();
System.out.println(sum.apply(2, 3)); // 5
Implementierung über eine anonyme Klasse
Operation multiply = new Operation() {
@Override
public int apply(int a, int b) {
return a * b;
}
};
System.out.println(multiply.apply(2, 3)); // 6
Hinweis zu Lambdas
Seit Java 8 lassen sich solche Schnittstellen praktisch mit Lambda-Ausdrücken implementieren – das ist eine kürzere Syntax. Wir werden Lambdas in ein paar Vorlesungen behandeln; fürs Erste genügt es zu wissen: Funktionale Schnittstellen sind genau dafür gedacht, möglichst bequem damit zu arbeiten.
4. Praxis: Schreiben Sie Ihre eigene funktionale Schnittstelle
Aufgabe 1. Erstellen Sie Ihr eigenes Action!
Erstellen Sie die Schnittstelle Action, die eine Zeichenkette entgegennimmt und nichts zurückgibt. Implementieren Sie sie über eine anonyme Klasse, die die Zeichenkette in Großbuchstaben ausgibt.
@FunctionalInterface
interface Action {
void act(String s);
}
public class ActionDemo {
public static void main(String[] args) {
Action shout = new Action() {
@Override
public void act(String text) {
System.out.println(text.toUpperCase());
}
};
shout.act("ich lerne java!"); // ICH LERNE JAVA!
}
}
(Später sehen wir, wie man das mit Lambda-Ausdrücken noch kürzer schreiben kann.)
Aufgabe 2. Zahlenfilter
Erstellen Sie die Schnittstelle NumberPredicate mit der Methode boolean test(int n). Implementieren Sie die Prüfung auf Gerade mit einer anonymen Klasse.
@FunctionalInterface
interface NumberPredicate {
boolean test(int n);
}
public class PredicateDemo {
public static void main(String[] args) {
NumberPredicate isEven = new NumberPredicate() {
@Override
public boolean test(int n) {
return n % 2 == 0;
}
};
System.out.println(isEven.test(4)); // true
System.out.println(isEven.test(7)); // false
}
}
Aufgabe 3. Nutzung der Standard-Schnittstellen
Anstelle einer eigenen Schnittstelle kann man den fertigen Predicate<Integer> verwenden:
import java.util.function.Predicate;
Predicate<Integer> isPositive = new Predicate<Integer>() {
@Override
public boolean test(Integer x) {
return x > 0;
}
};
System.out.println(isPositive.test(10)); // true
System.out.println(isPositive.test(-5)); // false
Tabelle: Funktionale Schnittstellen aus der Standardbibliothek
| Schnittstelle | Methode | Beschreibung | Beispiel |
|---|---|---|---|
|
|
Aufgabe ohne Argumente und Ergebnis | Threads, Timer |
|
|
Aufgabe mit Ergebnis | Threads, ExecutorService |
|
|
Vergleich zweier Objekte | Sortieren von Sammlungen |
|
|
„Verbraucher“ eines Wertes | Iteration über eine Sammlung |
|
|
„Lieferant“ eines Wertes | Lazy-Initialisierung, Datengenerierung |
|
|
Funktion von T nach R | Datenumwandlung |
|
|
Bedingungsprüfung | Filtern von Sammlungen |
5. Typische Fehler beim Arbeiten mit funktionalen Schnittstellen
Fehler Nr. 1: eine zweite abstrakte Methode hinzugefügt. Wenn eine Schnittstelle mehr als eine abstrakte Methode hat, ist sie nicht mehr funktional. Der Compiler (insbesondere mit @FunctionalInterface) meldet den Fehler sofort.
Fehler Nr. 2: vergessen, dass default- und static-Methoden nicht als abstrakt gelten. Sie können sie bedenkenlos zu einer funktionalen Schnittstelle hinzufügen – das verletzt die Regel „genau eine abstrakte Methode“ nicht.
Fehler Nr. 3: die Methodensignatur falsch implementiert. Zum Beispiel fordert die Schnittstelle zwei Argumente, Sie haben die Methode aber nur mit einem geschrieben. Prüfen Sie immer die Signaturen.
Fehler Nr. 4: @FunctionalInterface nicht verwendet und die Schnittstelle versehentlich beschädigt. Ohne Annotation kann man versehentlich eine zusätzliche Methode hinzufügen – und dann lange suchen, warum der Code nicht funktioniert. Fügen Sie die Annotation am besten immer zur Klarheit hinzu.
GO TO FULL VERSION